diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/datasource/AuthRemoteDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/datasource/AuthRemoteDataSource.kt index c72bd0f6..8de92dc3 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/datasource/AuthRemoteDataSource.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/datasource/AuthRemoteDataSource.kt @@ -8,6 +8,6 @@ interface AuthRemoteDataSource { suspend fun login(socialAccessToken: String, loginRequestDto: LoginRequestDto): Result suspend fun submitAgreement(termsAgreementRequestDto: TermsAgreementRequestDto): Result suspend fun logout(): Result - suspend fun withdrawal(): Result + suspend fun withdrawal(reason: String): Result suspend fun reissueToken(refreshToken: String): Result } diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/datasourceimpl/AuthRemoteDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/datasourceimpl/AuthRemoteDataSourceImpl.kt index 9cae33e2..96a1e8b0 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/datasourceimpl/AuthRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/datasourceimpl/AuthRemoteDataSourceImpl.kt @@ -3,6 +3,7 @@ package com.threegap.bitnagil.data.auth.datasourceimpl import com.threegap.bitnagil.data.auth.datasource.AuthRemoteDataSource import com.threegap.bitnagil.data.auth.model.request.LoginRequestDto import com.threegap.bitnagil.data.auth.model.request.TermsAgreementRequestDto +import com.threegap.bitnagil.data.auth.model.request.WithdrawalReasonRequest import com.threegap.bitnagil.data.auth.model.response.LoginResponseDto import com.threegap.bitnagil.data.auth.service.AuthService import com.threegap.bitnagil.data.common.safeApiCall @@ -27,9 +28,9 @@ class AuthRemoteDataSourceImpl @Inject constructor( authService.postLogout() } - override suspend fun withdrawal(): Result = + override suspend fun withdrawal(reason: String): Result = safeUnitApiCall { - authService.postWithdrawal() + authService.postWithdrawal(WithdrawalReasonRequest(reason)) } override suspend fun reissueToken(refreshToken: String): Result = diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/model/request/WithdrawalReasonRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/model/request/WithdrawalReasonRequest.kt new file mode 100644 index 00000000..f2173f3c --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/model/request/WithdrawalReasonRequest.kt @@ -0,0 +1,9 @@ +package com.threegap.bitnagil.data.auth.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WithdrawalReasonRequest( + @SerialName("reasonOfWithdrawal") val reasonOfWithdrawal: String, +) diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt index 2dadacc3..5901ed92 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt @@ -29,8 +29,8 @@ class AuthRepositoryImpl @Inject constructor( } } - override suspend fun withdrawal(): Result { - return authRemoteDataSource.withdrawal().also { + override suspend fun withdrawal(reason: String): Result { + return authRemoteDataSource.withdrawal(reason).also { if (it.isSuccess) authLocalDataSource.clearAuthToken() } } diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/service/AuthService.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/service/AuthService.kt index 58aa0a71..e360c61c 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/service/AuthService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/service/AuthService.kt @@ -2,6 +2,7 @@ package com.threegap.bitnagil.data.auth.service import com.threegap.bitnagil.data.auth.model.request.LoginRequestDto import com.threegap.bitnagil.data.auth.model.request.TermsAgreementRequestDto +import com.threegap.bitnagil.data.auth.model.request.WithdrawalReasonRequest import com.threegap.bitnagil.data.auth.model.response.LoginResponseDto import com.threegap.bitnagil.network.model.BaseResponse import retrofit2.http.Body @@ -23,7 +24,7 @@ interface AuthService { ): BaseResponse @POST("/api/v1/auth/withdrawal") - suspend fun postWithdrawal(): BaseResponse + suspend fun postWithdrawal(@Body request: WithdrawalReasonRequest): BaseResponse @POST("/api/v1/auth/logout") suspend fun postLogout(): BaseResponse diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/auth/repository/AuthRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/auth/repository/AuthRepository.kt index cee11a5d..bbee06e5 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/auth/repository/AuthRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/auth/repository/AuthRepository.kt @@ -7,7 +7,7 @@ interface AuthRepository { suspend fun login(socialAccessToken: String, socialType: String): Result suspend fun submitAgreement(termsAgreement: TermsAgreement): Result suspend fun logout(): Result - suspend fun withdrawal(): Result + suspend fun withdrawal(reason: String): Result suspend fun reissueToken(refreshToken: String): Result suspend fun getRefreshToken(): String? diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/auth/usecase/WithdrawalUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/auth/usecase/WithdrawalUseCase.kt index 7e96ca67..956a80ed 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/auth/usecase/WithdrawalUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/auth/usecase/WithdrawalUseCase.kt @@ -6,5 +6,5 @@ import javax.inject.Inject class WithdrawalUseCase @Inject constructor( private val authRepository: AuthRepository, ) { - suspend operator fun invoke(): Result = authRepository.withdrawal() + suspend operator fun invoke(reason: String): Result = authRepository.withdrawal(reason) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalScreen.kt index 89737ec4..0c62953f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.platform.LocalFocusManager 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.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon @@ -40,22 +39,22 @@ import com.threegap.bitnagil.designsystem.component.atom.BitnagilSelectButtonCol import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -import com.threegap.bitnagil.presentation.common.flow.collectAsEffect import com.threegap.bitnagil.presentation.withdrawal.component.WithdrawalConfirmDialog -import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalIntent import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalReason import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalSideEffect import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalState +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect @Composable fun WithdrawalScreenContainer( + viewModel: WithdrawalViewModel = hiltViewModel(), navigateToBack: () -> Unit, navigateToLogin: () -> Unit, - viewModel: WithdrawalViewModel = hiltViewModel(), ) { - val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle() + val uiState by viewModel.collectAsState() - viewModel.sideEffectFlow.collectAsEffect { sideEffect -> + viewModel.collectSideEffect { sideEffect -> when (sideEffect) { is WithdrawalSideEffect.NavigateToBack -> navigateToBack() is WithdrawalSideEffect.NavigateToLogin -> navigateToLogin() @@ -64,16 +63,16 @@ fun WithdrawalScreenContainer( if (uiState.showSuccessDialog) { WithdrawalConfirmDialog( - onConfirm = { viewModel.sendIntent(WithdrawalIntent.OnSuccessDialogConfirm) }, + onConfirm = viewModel::navigateToLogin, ) } WithdrawalScreen( uiState = uiState, - onTermsToggle = { viewModel.sendIntent(WithdrawalIntent.OnTermsToggle) }, - onReasonSelect = { viewModel.sendIntent(WithdrawalIntent.OnReasonSelected(it)) }, - onCustomReasonChanged = { viewModel.sendIntent(WithdrawalIntent.OnCustomReasonChanged(it)) }, - onBackClick = { viewModel.sendIntent(WithdrawalIntent.OnBackClick) }, + onTermsToggle = viewModel::onTermsToggle, + onReasonSelect = viewModel::updateSelectedReason, + onCustomReasonChanged = viewModel::updateCustomReason, + onBackClick = viewModel::navigateToBack, onWithdrawalClick = viewModel::withdrawal, ) } @@ -94,7 +93,6 @@ private fun WithdrawalScreen( Column( modifier = Modifier .fillMaxSize() - .background(BitnagilTheme.colors.white) .statusBarsPadding() .windowInsetsPadding(WindowInsets.ime), ) { @@ -221,13 +219,11 @@ private fun WithdrawalScreen( } } -@Preview +@Preview(showBackground = true) @Composable private fun WithdrawalScreenPreview() { WithdrawalScreen( - uiState = WithdrawalState( - isTermsChecked = true, - ), + uiState = WithdrawalState.INIT, onTermsToggle = {}, onReasonSelect = {}, onCustomReasonChanged = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalViewModel.kt index 6455ee48..e71b6328 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/WithdrawalViewModel.kt @@ -1,72 +1,66 @@ package com.threegap.bitnagil.presentation.withdrawal -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.ViewModel import com.threegap.bitnagil.domain.auth.usecase.WithdrawalUseCase -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel -import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalIntent +import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalReason import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalSideEffect import com.threegap.bitnagil.presentation.withdrawal.model.WithdrawalState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import org.orbitmvi.orbit.syntax.Syntax +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container import javax.inject.Inject @HiltViewModel class WithdrawalViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, private val withdrawalUseCase: WithdrawalUseCase, -) : MviViewModel( - savedStateHandle = savedStateHandle, - initState = WithdrawalState(), -) { - override suspend fun Syntax.reduceState( - intent: WithdrawalIntent, - state: WithdrawalState, - ): WithdrawalState? { - val newState = when (intent) { - is WithdrawalIntent.UpdateLoading -> state.copy(isLoading = intent.isLoading) - is WithdrawalIntent.OnTermsToggle -> state.copy(isTermsChecked = !state.isTermsChecked) - is WithdrawalIntent.ShowSuccessDialog -> state.copy(showSuccessDialog = true) +) : ContainerHost, ViewModel() { - is WithdrawalIntent.OnCustomReasonChanged -> { - state.copy(customReasonText = intent.text) - } + override val container: Container = container(initialState = WithdrawalState.INIT) - is WithdrawalIntent.OnReasonSelected -> { - state.copy( - selectedReason = intent.reason, - customReasonText = "", - ) - } - - is WithdrawalIntent.OnBackClick -> { - sendSideEffect(WithdrawalSideEffect.NavigateToBack) - null - } + fun onTermsToggle() { + intent { + reduce { state.copy(isTermsChecked = !state.isTermsChecked) } + } + } - is WithdrawalIntent.OnSuccessDialogConfirm -> { - sendSideEffect(WithdrawalSideEffect.NavigateToLogin) - null - } + fun updateCustomReason(text: String) { + intent { + reduce { state.copy(customReasonText = text) } } + } - return newState + fun updateSelectedReason(reason: WithdrawalReason?) { + intent { + reduce { state.copy(selectedReason = reason, customReasonText = "") } + } } fun withdrawal() { - if (container.stateFlow.value.isLoading) return - sendIntent(WithdrawalIntent.UpdateLoading(true)) - viewModelScope.launch { - withdrawalUseCase().fold( + intent { + if (state.isLoading) return@intent + reduce { state.copy(isLoading = true) } + val reason = state.finalWithdrawalReason + withdrawalUseCase(reason).fold( onSuccess = { - sendIntent(WithdrawalIntent.UpdateLoading(false)) - sendIntent(WithdrawalIntent.ShowSuccessDialog) + reduce { state.copy(isLoading = false, showSuccessDialog = true) } }, onFailure = { - sendIntent(WithdrawalIntent.UpdateLoading(false)) + reduce { state.copy(isLoading = false) } }, ) } } + + fun navigateToBack() { + intent { + postSideEffect(WithdrawalSideEffect.NavigateToBack) + } + } + + fun navigateToLogin() { + intent { + postSideEffect(WithdrawalSideEffect.NavigateToLogin) + } + } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalIntent.kt deleted file mode 100644 index 54908d84..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalIntent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.threegap.bitnagil.presentation.withdrawal.model - -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent - -sealed class WithdrawalIntent : MviIntent { - data object OnTermsToggle : WithdrawalIntent() - data object OnBackClick : WithdrawalIntent() - data object ShowSuccessDialog : WithdrawalIntent() - data object OnSuccessDialogConfirm : WithdrawalIntent() - data class UpdateLoading(val isLoading: Boolean) : WithdrawalIntent() - data class OnReasonSelected(val reason: WithdrawalReason?) : WithdrawalIntent() - data class OnCustomReasonChanged(val text: String) : WithdrawalIntent() -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalSideEffect.kt index d8e26c10..76ab1d4b 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalSideEffect.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalSideEffect.kt @@ -1,8 +1,6 @@ package com.threegap.bitnagil.presentation.withdrawal.model -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect - -sealed interface WithdrawalSideEffect : MviSideEffect { +sealed interface WithdrawalSideEffect { data object NavigateToBack : WithdrawalSideEffect data object NavigateToLogin : WithdrawalSideEffect } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalState.kt index 79bbf035..d1cfdf56 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/withdrawal/model/WithdrawalState.kt @@ -1,19 +1,25 @@ package com.threegap.bitnagil.presentation.withdrawal.model -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState -import kotlinx.parcelize.Parcelize - -@Parcelize data class WithdrawalState( - val isLoading: Boolean = false, - val isTermsChecked: Boolean = false, - val selectedReason: WithdrawalReason? = null, - val customReasonText: String = "", - val showSuccessDialog: Boolean = false, -) : MviState { + val isLoading: Boolean, + val isTermsChecked: Boolean, + val selectedReason: WithdrawalReason?, + val customReasonText: String, + val showSuccessDialog: Boolean, +) { val isWithdrawalEnabled: Boolean get() = isTermsChecked && (selectedReason != null || customReasonText.isNotBlank()) val finalWithdrawalReason: String get() = selectedReason?.displayText ?: customReasonText + + companion object { + val INIT = WithdrawalState( + isLoading = false, + isTermsChecked = false, + selectedReason = null, + customReasonText = "", + showSuccessDialog = false, + ) + } }