diff --git a/build-logic/src/main/kotlin/missionmate.android.feature.gradle.kts b/build-logic/src/main/kotlin/missionmate.android.feature.gradle.kts index 9f74ffe..0942695 100644 --- a/build-logic/src/main/kotlin/missionmate.android.feature.gradle.kts +++ b/build-logic/src/main/kotlin/missionmate.android.feature.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(project(":core:domain:mission")) implementation(project(":core:domain:common")) implementation(project(":core:domain:auth")) + implementation(project(":core:domain:onboarding")) implementation(project(":core:ui")) -} \ No newline at end of file +} diff --git a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/component/dialog/InvalidMissionErrorDialog.kt b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/component/dialog/InvalidMissionErrorDialog.kt new file mode 100644 index 0000000..cb3227a --- /dev/null +++ b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/component/dialog/InvalidMissionErrorDialog.kt @@ -0,0 +1,26 @@ +package com.goalpanzi.mission_mate.feature.board.component.dialog + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.DialogProperties +import com.goalpanzi.mission_mate.core.designsystem.component.MissionMateDialog +import com.goalpanzi.mission_mate.feature.board.R + +@Composable +fun InvalidMissionErrorDialog( + onDismissRequest: () -> Unit, + onClickOk: () -> Unit, + modifier: Modifier = Modifier +) { + MissionMateDialog( + titleId = R.string.board_mission_not_exist, + onDismissRequest = onDismissRequest, + onClickOk = onClickOk, + okTextId = R.string.retry, + dialogProperties = DialogProperties( + usePlatformDefaultWidth = false, + dismissOnBackPress = false, + dismissOnClickOutside = false + ) + ) +} diff --git a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/model/MissionError.kt b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/model/MissionError.kt index 2d8e7bd..1b1c46b 100644 --- a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/model/MissionError.kt +++ b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/model/MissionError.kt @@ -1,5 +1,5 @@ package com.goalpanzi.mission_mate.feature.board.model enum class MissionError { - NOT_EXIST, -} \ No newline at end of file + NOT_EXIST, INVALID_MISSION +} diff --git a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardScreen.kt b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardScreen.kt index a849244..05eeab6 100644 --- a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardScreen.kt +++ b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardScreen.kt @@ -51,6 +51,8 @@ import com.goalpanzi.mission_mate.feature.board.model.uimodel.MissionBoardUiMode import com.goalpanzi.mission_mate.feature.board.model.uimodel.MissionUiModel import com.goalpanzi.mission_mate.feature.board.model.uimodel.MissionVerificationUiModel import com.goalpanzi.mission_mate.core.designsystem.component.StableImage +import com.goalpanzi.mission_mate.feature.board.component.dialog.InvalidMissionErrorDialog +import com.goalpanzi.mission_mate.feature.board.model.MissionError import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -83,6 +85,7 @@ fun BoardRoute( onRefresh = viewModel::refreshMissionData ) var isShownDeleteMissionDialog by remember { mutableStateOf(false) } + var isShownNotLoadedErrorDialog by remember { mutableStateOf(false) } var isShownBoardRewardDialog by remember { mutableStateOf(null) } var isShownInvitationCodeDialog by remember { mutableStateOf(false) } @@ -123,11 +126,19 @@ fun BoardRoute( } } launch { - viewModel.missionError.collect { - if(it != null){ - Toast.makeText(context,context.getString(R.string.board_mission_not_exist), Toast.LENGTH_SHORT).show() - onNavigateOnboarding() - return@collect + viewModel.missionError.collect { error -> + when(error){ + MissionError.INVALID_MISSION -> { + isShownNotLoadedErrorDialog = true + viewModel.resetMissionError() + } + MissionError.NOT_EXIST -> { + Toast.makeText(context,context.getString(R.string.board_mission_not_exist), Toast.LENGTH_SHORT).show() + viewModel.resetMissionError() + onNavigateOnboarding() + return@collect + } + else -> { } } } } @@ -141,6 +152,18 @@ fun BoardRoute( } } + if (isShownNotLoadedErrorDialog) { + InvalidMissionErrorDialog( + onDismissRequest = { + isShownNotLoadedErrorDialog = false + }, + onClickOk = { + isShownNotLoadedErrorDialog = false + viewModel.fetchMissionData() + } + ) + } + if (isShownDeleteMissionDialog) { DeleteMissionDialog( onDismissRequest = { diff --git a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardViewModel.kt b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardViewModel.kt index e35552c..3c08007 100644 --- a/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardViewModel.kt +++ b/feature/board/src/main/java/com/goalpanzi/mission_mate/feature/board/screen/BoardViewModel.kt @@ -4,14 +4,15 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goalpanzi.mission_mate.core.domain.common.DomainResult +import com.goalpanzi.mission_mate.core.domain.common.model.mission.MissionStatus.Companion.statusString import com.goalpanzi.mission_mate.core.domain.mission.model.BoardReward import com.goalpanzi.mission_mate.core.domain.mission.usecase.DeleteMissionUseCase import com.goalpanzi.mission_mate.core.domain.mission.usecase.GetMissionBoardsUseCase import com.goalpanzi.mission_mate.core.domain.mission.usecase.GetMissionUseCase import com.goalpanzi.mission_mate.core.domain.mission.usecase.GetMissionVerificationsUseCase import com.goalpanzi.mission_mate.core.domain.mission.usecase.GetMyMissionVerificationUseCase -import com.goalpanzi.mission_mate.core.domain.mission.usecase.VerifyMissionUseCase import com.goalpanzi.mission_mate.core.domain.mission.usecase.ViewVerificationUseCase +import com.goalpanzi.mission_mate.core.domain.onboarding.usecase.GetJoinedMissionsUseCase import com.goalpanzi.mission_mate.core.domain.setting.usecase.GetViewedTooltipUseCase import com.goalpanzi.mission_mate.core.domain.setting.usecase.SetViewedTooltipUseCase import com.goalpanzi.mission_mate.core.domain.user.usecase.GetCachedMemberIdUseCase @@ -43,6 +44,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -59,8 +61,8 @@ class BoardViewModel @Inject constructor( private val deleteMissionUseCase: DeleteMissionUseCase, private val profileUseCase: ProfileUseCase, private val setViewedTooltipUseCase: SetViewedTooltipUseCase, - private val verifyMissionUseCase: VerifyMissionUseCase, - private val getMyMissionVerificationUseCase : GetMyMissionVerificationUseCase, + private val getJoinedMissionsUseCase: GetJoinedMissionsUseCase, + private val getMyMissionVerificationUseCase: GetMyMissionVerificationUseCase, private val viewVerificationUseCase: ViewVerificationUseCase ) : ViewModel() { @@ -68,7 +70,7 @@ class BoardViewModel @Inject constructor( val viewedToolTip: StateFlow = getViewedTooltipUseCase().stateIn( viewModelScope, - started = SharingStarted.WhileSubscribed(500), + started = SharingStarted.WhileSubscribed(5_000), initialValue = true ) @@ -106,7 +108,7 @@ class BoardViewModel @Inject constructor( memberId == mission.missionDetail.hostMemberId }.stateIn( viewModelScope, - started = SharingStarted.WhileSubscribed(500), + started = SharingStarted.WhileSubscribed(5_000), initialValue = false ) @@ -119,7 +121,7 @@ class BoardViewModel @Inject constructor( getMissionState(missionBoard, mission, missionVerification) }.stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(500), + started = SharingStarted.WhileSubscribed(5_000), initialValue = MissionState.LOADING ) private val _boardRewardEvent = MutableSharedFlow() @@ -157,7 +159,13 @@ class BoardViewModel @Inject constructor( is DomainResult.Success -> { val myMemberId = getCachedMemberIdUseCase().first() - _missionBoardUiModel.emit(MissionBoardUiModel.Success(it.data.toUiModel(myMemberId))) + _missionBoardUiModel.emit( + MissionBoardUiModel.Success( + it.data.toUiModel( + myMemberId + ) + ) + ) val boardPieceList = it.data.toUiModel(myMemberId).toBoardPieces(profileUseCase.getProfile()) @@ -166,7 +174,7 @@ class BoardViewModel @Inject constructor( _boardPieces.emit(boardPieceList) } else { val isMoved = - boardPieces.value.find { it.isMe }?.index?.plus(1) == boardPieceList.find { it.isMe }?.index + boardPieces.value.find { it.isMe }?.index?.plus(1) == boardPieceList.find { it.isMe }?.index val newList = PieceManager.getBoardPieces( boardPieces.value, @@ -175,7 +183,7 @@ class BoardViewModel @Inject constructor( _boardPieces.emit( newList ) - if(isMoved){ + if (isMoved) { delay(550) _boardRewardEvent.emit( it.data.missionBoards.find { it.isMyPosition }?.reward @@ -186,7 +194,7 @@ class BoardViewModel @Inject constructor( else -> { _missionBoardUiModel.emit(MissionBoardUiModel.Error) - _missionError.emit(MissionError.NOT_EXIST) + handleMissionError(isSameAsLastMission()) } } } @@ -203,7 +211,7 @@ class BoardViewModel @Inject constructor( else -> { _missionUiModel.emit(MissionUiModel.Error) - _missionError.emit(MissionError.NOT_EXIST) + handleMissionError(isSameAsLastMission()) } } } @@ -220,7 +228,7 @@ class BoardViewModel @Inject constructor( else -> { _missionVerificationUiModel.emit(MissionVerificationUiModel.Error) - _missionError.emit(MissionError.NOT_EXIST) + handleMissionError(isSameAsLastMission()) } } } @@ -275,13 +283,32 @@ class BoardViewModel @Inject constructor( } } } - - fun viewVerification(missionVerificationId : Long){ + + fun viewVerification(missionVerificationId: Long) { viewModelScope.launch { viewVerificationUseCase(missionVerificationId) - .collectLatest { - getMissionVerification() + .collectLatest { + getMissionVerification() } } } + + fun resetMissionError() { + _missionError.value = null + } + + private suspend fun handleMissionError(isSameAsLastMission : Boolean){ + if(isSameAsLastMission) _missionError.emit(MissionError.INVALID_MISSION) + else _missionError.emit(MissionError.NOT_EXIST) + } + + private suspend fun isSameAsLastMission(): Boolean { + return when (val result = getJoinedMissionsUseCase(filter = statusString).firstOrNull()) { + is DomainResult.Success -> { + result.data.missions.lastOrNull()?.missionId == missionId + } + + else -> false + } + } } diff --git a/feature/board/src/main/res/values/strings.xml b/feature/board/src/main/res/values/strings.xml index 7d45613..e62eb5b 100644 --- a/feature/board/src/main/res/values/strings.xml +++ b/feature/board/src/main/res/values/strings.xml @@ -53,11 +53,13 @@ 인증횟수(보드판 수)는 총 %d개 삭제하기 - 미션을 불러올 수 없습니다. + 미션을 불러올 수 없어요. 에러가 발생하였습니다. 확인 취소 닫기 업로드 + + 재시도