diff --git a/core/data/src/main/java/com/goalpanzi/mission_mate/core/data/repository/OnboardingRepositoryImpl.kt b/core/data/src/main/java/com/goalpanzi/mission_mate/core/data/repository/OnboardingRepositoryImpl.kt index 7f719df7..24dbfaac 100644 --- a/core/data/src/main/java/com/goalpanzi/mission_mate/core/data/repository/OnboardingRepositoryImpl.kt +++ b/core/data/src/main/java/com/goalpanzi/mission_mate/core/data/repository/OnboardingRepositoryImpl.kt @@ -6,6 +6,7 @@ import com.luckyoct.core.model.base.NetworkResult import com.luckyoct.core.model.request.CreateMissionRequest import com.luckyoct.core.model.request.JoinMissionRequest import com.luckyoct.core.model.response.MissionDetailResponse +import com.luckyoct.core.model.response.MissionsResponse import javax.inject.Inject class OnboardingRepositoryImpl @Inject constructor( @@ -22,4 +23,8 @@ class OnboardingRepositoryImpl @Inject constructor( override suspend fun joinMission(invitationCode: String): NetworkResult = handleResult { onboardingService.joinMission(JoinMissionRequest(invitationCode)) } + + override suspend fun getJoinedMissions(): NetworkResult = handleResult { + onboardingService.getJoinedMissions() + } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/repository/OnboardingRepository.kt b/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/repository/OnboardingRepository.kt index 8d2363ca..8849f31a 100644 --- a/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/repository/OnboardingRepository.kt +++ b/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/repository/OnboardingRepository.kt @@ -4,6 +4,7 @@ import com.goalpanzi.mission_mate.core.network.ResultHandler import com.luckyoct.core.model.base.NetworkResult import com.luckyoct.core.model.request.CreateMissionRequest import com.luckyoct.core.model.response.MissionDetailResponse +import com.luckyoct.core.model.response.MissionsResponse interface OnboardingRepository : ResultHandler { suspend fun createMission( @@ -17,4 +18,6 @@ interface OnboardingRepository : ResultHandler { suspend fun joinMission( invitationCode: String ) : NetworkResult + + suspend fun getJoinedMissions() : NetworkResult } \ No newline at end of file diff --git a/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/usecase/GetJoinedMissionsUseCase.kt b/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/usecase/GetJoinedMissionsUseCase.kt new file mode 100644 index 00000000..7dfebbcd --- /dev/null +++ b/core/domain/src/main/java/com/goalpanzi/mission_mate/core/domain/usecase/GetJoinedMissionsUseCase.kt @@ -0,0 +1,16 @@ +package com.goalpanzi.mission_mate.core.domain.usecase + +import com.goalpanzi.mission_mate.core.domain.repository.OnboardingRepository +import com.luckyoct.core.model.base.NetworkResult +import com.luckyoct.core.model.response.MissionsResponse +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class GetJoinedMissionsUseCase @Inject constructor( + private val onboardingRepository: OnboardingRepository +) { + operator fun invoke(): Flow> = flow { + emit(onboardingRepository.getJoinedMissions()) + } +} \ No newline at end of file diff --git a/core/model/src/main/java/com/luckyoct/core/model/response/MissionResponse.kt b/core/model/src/main/java/com/luckyoct/core/model/response/MissionResponse.kt new file mode 100644 index 00000000..d707a1af --- /dev/null +++ b/core/model/src/main/java/com/luckyoct/core/model/response/MissionResponse.kt @@ -0,0 +1,9 @@ +package com.luckyoct.core.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class MissionResponse( + val missionId : Long, + val description : String +) diff --git a/core/model/src/main/java/com/luckyoct/core/model/response/MissionsResponse.kt b/core/model/src/main/java/com/luckyoct/core/model/response/MissionsResponse.kt new file mode 100644 index 00000000..4ccc015a --- /dev/null +++ b/core/model/src/main/java/com/luckyoct/core/model/response/MissionsResponse.kt @@ -0,0 +1,9 @@ +package com.luckyoct.core.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class MissionsResponse( + val profile : ProfileResponse, + val missions : List +) diff --git a/core/model/src/main/java/com/luckyoct/core/model/response/ProfileResponse.kt b/core/model/src/main/java/com/luckyoct/core/model/response/ProfileResponse.kt new file mode 100644 index 00000000..9847f248 --- /dev/null +++ b/core/model/src/main/java/com/luckyoct/core/model/response/ProfileResponse.kt @@ -0,0 +1,9 @@ +package com.luckyoct.core.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class ProfileResponse( + val nickname : String, + val characterType : String +) diff --git a/core/network/src/main/java/com/goalpanzi/mission_mate/core/network/service/OnboardingService.kt b/core/network/src/main/java/com/goalpanzi/mission_mate/core/network/service/OnboardingService.kt index 946c5eb3..7a3d207c 100644 --- a/core/network/src/main/java/com/goalpanzi/mission_mate/core/network/service/OnboardingService.kt +++ b/core/network/src/main/java/com/goalpanzi/mission_mate/core/network/service/OnboardingService.kt @@ -3,9 +3,9 @@ package com.goalpanzi.mission_mate.core.network.service import com.luckyoct.core.model.request.CreateMissionRequest import com.luckyoct.core.model.request.JoinMissionRequest import com.luckyoct.core.model.response.MissionDetailResponse +import com.luckyoct.core.model.response.MissionsResponse import retrofit2.Response import retrofit2.http.Body -import retrofit2.http.FormUrlEncoded import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Query @@ -25,4 +25,7 @@ interface OnboardingService { suspend fun joinMission( @Body request : JoinMissionRequest ) : Response + + @GET("/api/mission-members/me") + suspend fun getJoinedMissions() : Response } \ No newline at end of file diff --git a/feature/main/src/main/java/com/goalpanzi/mission_mate/core/main/component/MainNavHost.kt b/feature/main/src/main/java/com/goalpanzi/mission_mate/core/main/component/MainNavHost.kt index 402f33b5..f6a22896 100644 --- a/feature/main/src/main/java/com/goalpanzi/mission_mate/core/main/component/MainNavHost.kt +++ b/feature/main/src/main/java/com/goalpanzi/mission_mate/core/main/component/MainNavHost.kt @@ -37,7 +37,10 @@ internal fun MainNavHost( onboardingNavGraph( onClickBoardSetup = { navigator.navigationToBoardSetup() }, onClickInvitationCode = { navigator.navigationToInvitationCode() }, - onClickSetting = { } + onClickSetting = { }, + onNavigateMissionBoard = { missionId -> + + } ) boardSetupNavGraph( onSuccess = { diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/OnboardingNavigation.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/OnboardingNavigation.kt index bd72e134..ea55b992 100644 --- a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/OnboardingNavigation.kt +++ b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/OnboardingNavigation.kt @@ -29,13 +29,15 @@ fun NavController.navigateToInvitationCode() { fun NavGraphBuilder.onboardingNavGraph( onClickBoardSetup: () -> Unit, onClickInvitationCode: () -> Unit, - onClickSetting: () -> Unit + onClickSetting: () -> Unit, + onNavigateMissionBoard : (Long) -> Unit ) { composable { OnboardingRoute( onClickBoardSetup = onClickBoardSetup, onClickInvitationCode = onClickInvitationCode, - onClickSetting = onClickSetting + onClickSetting = onClickSetting, + onNavigateMissionBoard = onNavigateMissionBoard ) } } diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/CodeResultType.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/CodeResultEvent.kt similarity index 100% rename from feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/CodeResultType.kt rename to feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/CodeResultEvent.kt diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/JoinResultType.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/JoinResultEvent.kt similarity index 100% rename from feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/JoinResultType.kt rename to feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/JoinResultEvent.kt diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/OnboardingResultEvent.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/OnboardingResultEvent.kt new file mode 100644 index 00000000..62c8dc56 --- /dev/null +++ b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/OnboardingResultEvent.kt @@ -0,0 +1,8 @@ +package com.goalpanzi.mission_mate.feature.onboarding.model + +import com.luckyoct.core.model.response.MissionResponse + +sealed class OnboardingResultEvent { + data class SuccessWithJoinedMissions(val mission : MissionResponse) : OnboardingResultEvent() + data object Error : OnboardingResultEvent() +} diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/ProfileUiModel.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/ProfileUiModel.kt new file mode 100644 index 00000000..2f534022 --- /dev/null +++ b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/model/ProfileUiModel.kt @@ -0,0 +1,8 @@ +package com.goalpanzi.mission_mate.feature.onboarding.model + +import com.luckyoct.core.model.response.ProfileResponse + +sealed class OnboardingUiModel { + data object Loading : OnboardingUiModel() + data class Success(val profileResponse: ProfileResponse) : OnboardingUiModel() +} diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingScreen.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingScreen.kt index 085d39d7..57b05de7 100644 --- a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingScreen.kt +++ b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingScreen.kt @@ -13,10 +13,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton 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.graphics.Color @@ -28,6 +31,8 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.goalpanzi.mission_mate.core.designsystem.theme.ColorGray1_FF404249 import com.goalpanzi.mission_mate.core.designsystem.theme.ColorWhite_FFFFFFFF import com.goalpanzi.mission_mate.core.designsystem.theme.MissionMateTypography @@ -37,6 +42,9 @@ import com.goalpanzi.mission_mate.feature.onboarding.R import com.goalpanzi.mission_mate.feature.onboarding.component.OnboardingNavigationButton import com.goalpanzi.mission_mate.feature.onboarding.component.OutlinedTextBox import com.goalpanzi.mission_mate.feature.onboarding.component.StableImage +import com.goalpanzi.mission_mate.feature.onboarding.model.OnboardingResultEvent +import com.goalpanzi.mission_mate.feature.onboarding.model.OnboardingUiModel +import com.luckyoct.core.model.response.ProfileResponse import com.goalpanzi.mission_mate.core.designsystem.R as designSystemResource @Composable @@ -44,9 +52,29 @@ fun OnboardingRoute( modifier: Modifier = Modifier, onClickBoardSetup: () -> Unit, onClickInvitationCode: () -> Unit, - onClickSetting: () -> Unit + onClickSetting: () -> Unit, + onNavigateMissionBoard : (Long) -> Unit, + viewModel: OnboardingViewModel = hiltViewModel() ) { + val onboardingUiModel by viewModel.onboardingUiModel.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = Unit) { + viewModel.getJoinedMissions() + + viewModel.onboardingResultEvent.collect { result -> + when(result){ + is OnboardingResultEvent.SuccessWithJoinedMissions -> { + onNavigateMissionBoard(result.mission.missionId) + } + is OnboardingResultEvent.Error -> { + // 에러 + } + } + } + } + OnboardingScreen( + onboardingUiModel = onboardingUiModel, modifier = modifier.fillMaxSize(), onClickBoardSetup = onClickBoardSetup, onClickInvitationCode = onClickInvitationCode, @@ -56,10 +84,11 @@ fun OnboardingRoute( @Composable fun OnboardingScreen( - modifier: Modifier = Modifier, + onboardingUiModel: OnboardingUiModel, onClickBoardSetup: () -> Unit, onClickInvitationCode: () -> Unit, - onClickSetting: () -> Unit + onClickSetting: () -> Unit, + modifier: Modifier = Modifier, ) { Box( modifier = modifier.background(ColorWhite_FFFFFFFF) @@ -72,78 +101,96 @@ fun OnboardingScreen( contentDescription = null, contentScale = ContentScale.FillWidth ) - Column( - modifier = modifier - .statusBarsPadding() - .navigationBarsPadding(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - MissionMateTopAppBar( - modifier = modifier, - navigationType = NavigationType.NONE, - containerColor = Color.Transparent, - rightActionButtons = { - TopBarSetting( - onClick = {} + when(onboardingUiModel){ + is OnboardingUiModel.Success -> { + Column( + modifier = modifier + .statusBarsPadding() + .navigationBarsPadding(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MissionMateTopAppBar( + modifier = modifier, + navigationType = NavigationType.NONE, + containerColor = Color.Transparent, + rightActionButtons = { + TopBarSetting( + onClick = {} + ) + } ) + Text( + modifier = Modifier.padding(bottom = 52.dp), + text = stringResource(id = R.string.onboarding_ready_title), + textAlign = TextAlign.Center, + style = MissionMateTypography.heading_sm_regular, + color = ColorGray1_FF404249 + ) + OutlinedTextBox( + text = stringResource(id = R.string.onboarding_level_1), + modifier = Modifier.padding(bottom = 23.dp) + ) + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 7.dp) + .wrapContentHeight(), + contentAlignment = Alignment.BottomCenter + ) { + StableImage( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + drawableResId = designSystemResource.drawable.img_jeju_theme, + contentScale = ContentScale.FillWidth + ) + StableImage( + modifier = Modifier + .fillMaxWidth(0.564f) + .wrapContentHeight(), + drawableResId = when(onboardingUiModel.profileResponse.characterType){ + "CAT" -> designSystemResource.drawable.img_cat_selected + "DOG" -> designSystemResource.drawable.img_dog_selected + "RABBIT" -> designSystemResource.drawable.img_rabbit_selected + "BEAR" -> designSystemResource.drawable.img_bear_selected + "PANDA" -> designSystemResource.drawable.img_panda_selected + "BIRD" -> designSystemResource.drawable.img_bird_selected + else -> designSystemResource.drawable.img_rabbit_selected + }, + contentScale = ContentScale.FillWidth + ) + } + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(18.dp) + ) { + OnboardingNavigationButton( + modifier = Modifier.weight(1f), + titleId = R.string.onboarding_crating_board_title, + descriptionId = R.string.onboarding_crating_board_desription, + imageId = designSystemResource.drawable.ic_creating_board, + onClick = onClickBoardSetup + ) + OnboardingNavigationButton( + modifier = Modifier.weight(1f), + titleId = R.string.onboarding_code_title, + descriptionId = R.string.onboarding_code_desription, + imageId = designSystemResource.drawable.ic_invitation_friend, + onClick = onClickInvitationCode + ) + } } - ) - Text( - modifier = Modifier.padding(bottom = 52.dp), - text = stringResource(id = R.string.onboarding_ready_title), - textAlign = TextAlign.Center, - style = MissionMateTypography.heading_sm_regular, - color = ColorGray1_FF404249 - ) - OutlinedTextBox( - text = stringResource(id = R.string.onboarding_level_1), - modifier = Modifier.padding(bottom = 23.dp) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 7.dp) - .wrapContentHeight(), - contentAlignment = Alignment.BottomCenter - ) { - StableImage( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - drawableResId = designSystemResource.drawable.img_jeju_theme, - contentScale = ContentScale.FillWidth - ) - StableImage( - modifier = Modifier - .fillMaxWidth(0.564f) - .wrapContentHeight(), - drawableResId = designSystemResource.drawable.img_rabbit_selected, - contentScale = ContentScale.FillWidth - ) } - Row( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 24.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(18.dp) - ) { - OnboardingNavigationButton( - modifier = Modifier.weight(1f), - titleId = R.string.onboarding_crating_board_title, - descriptionId = R.string.onboarding_crating_board_desription, - imageId = designSystemResource.drawable.ic_creating_board, - onClick = onClickBoardSetup - ) - OnboardingNavigationButton( - modifier = Modifier.weight(1f), - titleId = R.string.onboarding_code_title, - descriptionId = R.string.onboarding_code_desription, - imageId = designSystemResource.drawable.ic_invitation_friend, - onClick = onClickInvitationCode + is OnboardingUiModel.Loading -> { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) ) } } + } } @@ -167,6 +214,12 @@ fun TopBarSetting( @Composable fun OnboardingScreenPreview() { OnboardingScreen( + onboardingUiModel = OnboardingUiModel.Success( + ProfileResponse( + nickname = "Test", + characterType = "CAT" + ) + ), onClickBoardSetup = {}, onClickInvitationCode = {}, onClickSetting = {} diff --git a/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingViewModel.kt b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingViewModel.kt new file mode 100644 index 00000000..5c151100 --- /dev/null +++ b/feature/onboarding/src/main/java/com/goalpanzi/mission_mate/feature/onboarding/screen/OnboardingViewModel.kt @@ -0,0 +1,60 @@ +package com.goalpanzi.mission_mate.feature.onboarding.screen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.goalpanzi.mission_mate.core.domain.usecase.GetJoinedMissionsUseCase +import com.goalpanzi.mission_mate.feature.onboarding.model.OnboardingResultEvent +import com.goalpanzi.mission_mate.feature.onboarding.model.OnboardingUiModel +import com.luckyoct.core.model.base.NetworkResult +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class OnboardingViewModel @Inject constructor( + private val getJoinedMissionsUseCase : GetJoinedMissionsUseCase +) : ViewModel() { + + private val _onboardingUiModel = MutableStateFlow(OnboardingUiModel.Loading) + val onboardingUiModel : StateFlow = _onboardingUiModel.asStateFlow() + + private val _onboardingResultEvent = MutableSharedFlow() + val onboardingResultEvent : SharedFlow = _onboardingResultEvent.asSharedFlow() + + fun getJoinedMissions() { + viewModelScope.launch { + _onboardingUiModel.emit(OnboardingUiModel.Loading) + getJoinedMissionsUseCase() + .catch { + _onboardingResultEvent.emit(OnboardingResultEvent.Error) + }.collect { result -> + when(result){ + is NetworkResult.Success -> { + result.data.missions.let { missions -> + if(missions.isNotEmpty()){ + _onboardingResultEvent.emit( + OnboardingResultEvent.SuccessWithJoinedMissions(missions.first()) + ) + }else { + _onboardingUiModel.emit( + OnboardingUiModel.Success(result.data.profile) + ) + } + } + } + else -> { + _onboardingResultEvent.emit(OnboardingResultEvent.Error) + } + } + } + + } + } +} \ No newline at end of file