diff --git a/core/data-remote/src/main/java/com/kikidan/data_remote/di/NetworkExtension.kt b/core/data-remote/src/main/java/com/kikidan/data_remote/di/NetworkExtension.kt index 73d3773b..98e1951e 100644 --- a/core/data-remote/src/main/java/com/kikidan/data_remote/di/NetworkExtension.kt +++ b/core/data-remote/src/main/java/com/kikidan/data_remote/di/NetworkExtension.kt @@ -23,9 +23,9 @@ internal fun HttpClientConfig<*>.installTodakunDefaults( json(json) } install(HttpTimeout) { - requestTimeoutMillis = 15_000 - connectTimeoutMillis = 10_000 - socketTimeoutMillis = 15_000 + requestTimeoutMillis = 100_000 + connectTimeoutMillis = 100_000 + socketTimeoutMillis = 100_000 } install(DefaultRequest) { url(baseUrl) diff --git a/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingRoute.kt b/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingRoute.kt index ec660e4a..9ca2d50c 100644 --- a/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingRoute.kt +++ b/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingRoute.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -43,15 +42,12 @@ fun OnboardingRoute( viewModel: OnboardingViewModel = hiltViewModel(), ) { val state by viewModel.collectAsState() - var permissionHandled by rememberSaveable { - mutableStateOf(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) - } val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), ) { - // 권한을 허락하지 않더라도 앱 진입 - permissionHandled = true + // 권한을 허락하지 않더라도 회원가입 진행 + viewModel.confirmComplete(onboardingToken) } val signupErrorMessage = stringResource(R.string.onboarding_signup_error) val invalidInputErrorMessage = stringResource(R.string.onboarding_invalid_input_error) @@ -70,7 +66,7 @@ fun OnboardingRoute( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } else { - onFinish() + viewModel.confirmComplete(onboardingToken) } } @@ -129,21 +125,18 @@ fun OnboardingRoute( isSubmitting = state.submitState is OnboardingSubmitState.Loading, onLifeStageSelect = viewModel::selectLifeStage, onRelationshipStatusSelect = viewModel::selectRelationshipStatus, - onNextClick = { - viewModel.confirmComplete(onboardingToken) - }, + onNextClick = viewModel::clickComplete, onBackClick = viewModel::clickBack, modifier = modifier, ) } OnboardingStep.COMPLETE -> { - if (permissionHandled) { - CompleteScreen( - onFinish = onFinish, - modifier = modifier, - ) - } + CompleteScreen( + isLoading = state.submitState is OnboardingSubmitState.Loading, + onFinish = onFinish, + modifier = modifier, + ) } } diff --git a/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingViewModel.kt b/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingViewModel.kt index 428d3a2a..5281937c 100644 --- a/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/kikidan/onboarding/OnboardingViewModel.kt @@ -47,6 +47,11 @@ class OnboardingViewModel } } + fun clickComplete() = + intent { + postSideEffect(OnboardingSideEffect.PermissionRequest) + } + fun confirmComplete(onboardingToken: OnboardingToken) = intent { if (state.submitState is OnboardingSubmitState.Loading) return@intent @@ -55,15 +60,15 @@ class OnboardingViewModel postSideEffect(OnboardingSideEffect.InvalidInput) return@intent } - reduce { state.copy(submitState = OnboardingSubmitState.Loading) } + reduce { state.copy(submitState = OnboardingSubmitState.Loading, step = OnboardingStep.COMPLETE) } signUpUseCase( signupSubmission = signupSubmission, onboardingToken = onboardingToken, ).onSuccess { - reduce { state.copy(submitState = OnboardingSubmitState.Success, step = OnboardingStep.COMPLETE) } + reduce { state.copy(submitState = OnboardingSubmitState.Success) } postSideEffect(OnboardingSideEffect.PermissionRequest) }.onFailure { e -> - reduce { state.copy(submitState = OnboardingSubmitState.Failure) } + reduce { state.copy(submitState = OnboardingSubmitState.Failure, step = OnboardingStep.EXTRA_QUESTION) } postSideEffect(OnboardingSideEffect.Failure(e)) } } diff --git a/feature/onboarding/src/main/java/com/kikidan/onboarding/screen/CompleteScreen.kt b/feature/onboarding/src/main/java/com/kikidan/onboarding/screen/CompleteScreen.kt index 6e0d6606..0718e974 100644 --- a/feature/onboarding/src/main/java/com/kikidan/onboarding/screen/CompleteScreen.kt +++ b/feature/onboarding/src/main/java/com/kikidan/onboarding/screen/CompleteScreen.kt @@ -31,13 +31,16 @@ import kotlinx.coroutines.delay @Composable internal fun CompleteScreen( + isLoading: Boolean, onFinish: () -> Unit, modifier: Modifier = Modifier, ) { val currentOnFinish by rememberUpdatedState(onFinish) - LaunchedEffect(Unit) { - delay(3000) - currentOnFinish() + LaunchedEffect(isLoading) { + if (!isLoading) { + delay(3000L) + currentOnFinish() + } } Box( modifier = @@ -83,6 +86,7 @@ internal fun CompleteScreen( private fun CompleteScreenPreview() { TodakunTheme { CompleteScreen( + isLoading = false, onFinish = {}, ) } diff --git a/feature/onboarding/src/test/java/com/kikidan/onboarding/OnboardingViewModelTest.kt b/feature/onboarding/src/test/java/com/kikidan/onboarding/OnboardingViewModelTest.kt index c982c907..cfeb6fa3 100644 --- a/feature/onboarding/src/test/java/com/kikidan/onboarding/OnboardingViewModelTest.kt +++ b/feature/onboarding/src/test/java/com/kikidan/onboarding/OnboardingViewModelTest.kt @@ -14,7 +14,6 @@ import com.kikidan.onboarding.model.OnboardingSheet import com.kikidan.onboarding.model.OnboardingSideEffect import com.kikidan.onboarding.model.OnboardingState import com.kikidan.onboarding.model.OnboardingStep -import com.kikidan.onboarding.model.OnboardingSubmitState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -135,7 +134,17 @@ class OnboardingViewModelTest { } @Test - fun `마지막 스텝에서 다음을 누르면 완료 화면으로 전환된다`() = + fun `완료 단계에서 다음을 누르면 권한 요청 사이드이펙트가 먼저 전달된다`() = + runTest { + val initial = OnboardingState(step = OnboardingStep.EXTRA_QUESTION) + viewModel().test(this, initialState = initial) { + containerHost.clickComplete() + expectSideEffect(OnboardingSideEffect.PermissionRequest) + } + } + + @Test + fun `가입 요청을 시작하면 응답을 기다리는 동안 완료 화면으로 전환된다`() = runTest { val initial = OnboardingState( @@ -150,9 +159,8 @@ class OnboardingViewModelTest { ) viewModel().test(this, initialState = initial) { containerHost.confirmComplete(onboardingToken) - expectState { copy(submitState = OnboardingSubmitState.Loading) } - expectState { copy(submitState = OnboardingSubmitState.Success, step = OnboardingStep.COMPLETE) } - expectSideEffect(OnboardingSideEffect.PermissionRequest) + expectState { copy(isSubmitting = true, step = OnboardingStep.COMPLETE) } + expectState { copy(isSubmitting = false, step = OnboardingStep.COMPLETE) } } } @@ -169,7 +177,7 @@ class OnboardingViewModelTest { birthTime = BirthTime.JA, lifeStage = Job.STUDENT, relationshipStatus = RelationshipStatus.SOLO, - submitState = OnboardingSubmitState.Loading, + isSubmitting = true, ) viewModel().test(this, initialState = initial) { containerHost.confirmComplete(onboardingToken) @@ -196,8 +204,8 @@ class OnboardingViewModelTest { ) viewModel().test(this, initialState = initial) { containerHost.confirmComplete(onboardingToken) - expectState { copy(submitState = OnboardingSubmitState.Loading) } - expectState { copy(submitState = OnboardingSubmitState.Failure) } + expectState { copy(isSubmitting = true, step = OnboardingStep.COMPLETE) } + expectState { copy(isSubmitting = false, step = OnboardingStep.EXTRA_QUESTION) } expectSideEffect(OnboardingSideEffect.Failure(error)) } }