diff --git a/data/src/main/java/kr/genti/data/dataSource/InfoDataSource.kt b/data/src/main/java/kr/genti/data/dataSource/InfoDataSource.kt index 6107414e..9446690a 100644 --- a/data/src/main/java/kr/genti/data/dataSource/InfoDataSource.kt +++ b/data/src/main/java/kr/genti/data/dataSource/InfoDataSource.kt @@ -5,4 +5,8 @@ import kr.genti.data.dto.request.SignupRequestDto interface InfoDataSource { suspend fun postSignupData(request: SignupRequestDto): BaseResponse + + suspend fun postUserLogout(): BaseResponse + + suspend fun deleteUser(): BaseResponse } diff --git a/data/src/main/java/kr/genti/data/dataSourceImpl/InfoDataSourceImpl.kt b/data/src/main/java/kr/genti/data/dataSourceImpl/InfoDataSourceImpl.kt index 1981a13e..7a18076e 100644 --- a/data/src/main/java/kr/genti/data/dataSourceImpl/InfoDataSourceImpl.kt +++ b/data/src/main/java/kr/genti/data/dataSourceImpl/InfoDataSourceImpl.kt @@ -12,4 +12,8 @@ data class InfoDataSourceImpl private val infoService: InfoService, ) : InfoDataSource { override suspend fun postSignupData(request: SignupRequestDto): BaseResponse = infoService.postSignupData(request) + + override suspend fun postUserLogout(): BaseResponse = infoService.postUserLogout() + + override suspend fun deleteUser(): BaseResponse = infoService.deleteUser() } diff --git a/data/src/main/java/kr/genti/data/repositoryImpl/InfoRepositoryImpl.kt b/data/src/main/java/kr/genti/data/repositoryImpl/InfoRepositoryImpl.kt index 63e6eb28..15904fee 100644 --- a/data/src/main/java/kr/genti/data/repositoryImpl/InfoRepositoryImpl.kt +++ b/data/src/main/java/kr/genti/data/repositoryImpl/InfoRepositoryImpl.kt @@ -15,4 +15,14 @@ class InfoRepositoryImpl runCatching { infoDataSource.postSignupData(request.toDto()).response } + + override suspend fun postUserLogout(): Result = + runCatching { + infoDataSource.postUserLogout().response + } + + override suspend fun deleteUser(): Result = + runCatching { + infoDataSource.deleteUser().response + } } diff --git a/data/src/main/java/kr/genti/data/service/InfoService.kt b/data/src/main/java/kr/genti/data/service/InfoService.kt index 83772355..2beb36fd 100644 --- a/data/src/main/java/kr/genti/data/service/InfoService.kt +++ b/data/src/main/java/kr/genti/data/service/InfoService.kt @@ -3,6 +3,7 @@ package kr.genti.data.service import kr.genti.data.dto.BaseResponse import kr.genti.data.dto.request.SignupRequestDto import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.POST interface InfoService { @@ -10,4 +11,10 @@ interface InfoService { suspend fun postSignupData( @Body request: SignupRequestDto, ): BaseResponse + + @POST("api/v1/users/logout") + suspend fun postUserLogout(): BaseResponse + + @DELETE("api/v1/users") + suspend fun deleteUser(): BaseResponse } diff --git a/domain/src/main/kotlin/kr/genti/domain/repository/InfoRepository.kt b/domain/src/main/kotlin/kr/genti/domain/repository/InfoRepository.kt index 68706fca..674f7bbc 100644 --- a/domain/src/main/kotlin/kr/genti/domain/repository/InfoRepository.kt +++ b/domain/src/main/kotlin/kr/genti/domain/repository/InfoRepository.kt @@ -4,4 +4,8 @@ import kr.genti.domain.entity.request.SignupRequestModel interface InfoRepository { suspend fun postSignupData(request: SignupRequestModel): Result + + suspend fun postUserLogout(): Result + + suspend fun deleteUser(): Result } diff --git a/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashActivity.kt b/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashActivity.kt index c57d896b..155cac55 100644 --- a/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashActivity.kt +++ b/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashActivity.kt @@ -29,6 +29,7 @@ class SplashActivity : BaseActivity(R.layout.activity_spl setSystemWindowsTransparent() observeAutoLoginState() + observeReissueTokenResult() } private fun setSystemWindowsTransparent() { @@ -44,19 +45,35 @@ class SplashActivity : BaseActivity(R.layout.activity_spl viewModel.isAutoLogined.flowWithLifecycle(lifecycle).distinctUntilChanged() .onEach { isAutoLogined -> if (isAutoLogined) { + viewModel.postToReissueToken() + } else { + navigateToLoginView() + } + }.launchIn(lifecycleScope) + } + + private fun observeReissueTokenResult() { + viewModel.reissueTokenResult.flowWithLifecycle(lifecycle).distinctUntilChanged() + .onEach { isSuccess -> + if (isSuccess) { Intent(this, MainActivity::class.java).apply { startActivity(this) } + finish() } else { - Intent(this, LoginActivity::class.java).apply { - startActivity( - this, - ActivityOptions.makeCustomAnimation(this@SplashActivity, 0, 0) - .toBundle(), - ) - } + navigateToLoginView() } - finish() }.launchIn(lifecycleScope) } + + private fun navigateToLoginView() { + Intent(this, LoginActivity::class.java).apply { + startActivity( + this, + ActivityOptions.makeCustomAnimation(this@SplashActivity, 0, 0) + .toBundle(), + ) + } + finish() + } } diff --git a/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashViewModel.kt b/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashViewModel.kt index 11d8fe4b..bded8928 100644 --- a/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashViewModel.kt +++ b/presentation/src/main/java/kr/genti/presentation/auth/splash/SplashViewModel.kt @@ -7,6 +7,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch +import kr.genti.domain.entity.request.ReissueRequestModel +import kr.genti.domain.repository.AuthRepository import kr.genti.domain.repository.UserRepository import javax.inject.Inject @@ -15,10 +17,14 @@ class SplashViewModel @Inject constructor( private val userRepository: UserRepository, + private val authRepository: AuthRepository, ) : ViewModel() { private val _isAutoLogined = MutableSharedFlow() val isAutoLogined: SharedFlow = _isAutoLogined + private val _reissueTokenResult = MutableSharedFlow() + val reissueTokenResult: SharedFlow = _reissueTokenResult + init { getAutoLoginState() } @@ -34,6 +40,22 @@ class SplashViewModel } } + fun postToReissueToken() { + viewModelScope.launch { + authRepository.postReissueTokens( + ReissueRequestModel( + userRepository.getAccessToken(), + userRepository.getRefreshToken(), + ), + ).onSuccess { + userRepository.setTokens(it.accessToken, it.refreshToken) + _reissueTokenResult.emit(true) + }.onFailure { + _reissueTokenResult.emit(false) + } + } + } + companion object { private const val DELAY_SPLASH = 1500L private const val ROLE_USER = "USER" diff --git a/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt b/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt index 9152f229..30c41851 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt @@ -43,6 +43,11 @@ class MainActivity : BaseActivity(R.layout.activity_main) { observeResetResult() } + override fun onResume() { + super.onResume() + viewModel.getGenerateStatusFromServer() + } + fun initBnvItemIconTintList() { with(binding.bnvMain) { itemIconTintList = null diff --git a/presentation/src/main/java/kr/genti/presentation/main/MainViewModel.kt b/presentation/src/main/java/kr/genti/presentation/main/MainViewModel.kt index 27653207..df8cee90 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/MainViewModel.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/MainViewModel.kt @@ -26,11 +26,7 @@ class MainViewModel var currentStatus: GenerateStatus = GenerateStatus.NEW_REQUEST_AVAILABLE lateinit var newPicture: GenerateStatusModel - init { - getGenerateStatusFromServer() - } - - private fun getGenerateStatusFromServer() { + fun getGenerateStatusFromServer() { viewModelScope.launch { generateRepository.getGenerateStatus() .onSuccess { diff --git a/presentation/src/main/java/kr/genti/presentation/setting/SettingLogoutDialog.kt b/presentation/src/main/java/kr/genti/presentation/setting/SettingLogoutDialog.kt index 6fc92875..4a4c41da 100644 --- a/presentation/src/main/java/kr/genti/presentation/setting/SettingLogoutDialog.kt +++ b/presentation/src/main/java/kr/genti/presentation/setting/SettingLogoutDialog.kt @@ -5,12 +5,18 @@ import android.os.Bundle import android.view.View import android.view.WindowManager import androidx.fragment.app.activityViewModels +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kr.genti.core.base.BaseDialog import kr.genti.core.extension.setGusianBlur import kr.genti.core.extension.setOnSingleClickListener +import kr.genti.core.extension.stringOf +import kr.genti.core.extension.toast +import kr.genti.core.state.UiState import kr.genti.core.util.RestartUtil.restartApp import kr.genti.presentation.R import kr.genti.presentation.databinding.DialogSettingLogoutBinding @@ -39,6 +45,7 @@ class SettingLogoutDialog : initReturnBtnListener() initLogoutBtnListener() + observeUserLogoutState() } private fun initReturnBtnListener() { @@ -46,15 +53,26 @@ class SettingLogoutDialog : } private fun initLogoutBtnListener() { - // TODO : 토큰 설정 이후 로그아웃 설정 binding.btnLogout.setOnSingleClickListener { - lifecycleScope.launch { - delay(500) - restartApp(binding.root.context, null) - } + viewModel.logoutFromKakao() } } + private fun observeUserLogoutState() { + viewModel.userLogoutState.flowWithLifecycle(lifecycle).distinctUntilChanged() + .onEach { state -> + when (state) { + is UiState.Success -> { + delay(500) + restartApp(binding.root.context, null) + } + + is UiState.Failure -> toast(stringOf(R.string.error_msg)) + else -> return@onEach + } + }.launchIn(lifecycleScope) + } + override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) requireActivity().window.decorView.rootView.setGusianBlur(null) diff --git a/presentation/src/main/java/kr/genti/presentation/setting/SettingQuitDialog.kt b/presentation/src/main/java/kr/genti/presentation/setting/SettingQuitDialog.kt index a44b5a6b..9c87cdb1 100644 --- a/presentation/src/main/java/kr/genti/presentation/setting/SettingQuitDialog.kt +++ b/presentation/src/main/java/kr/genti/presentation/setting/SettingQuitDialog.kt @@ -5,13 +5,19 @@ import android.os.Bundle import android.view.View import android.view.WindowManager import androidx.fragment.app.activityViewModels +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kr.genti.core.base.BaseDialog import kr.genti.core.extension.setGusianBlur import kr.genti.core.extension.setOnSingleClickListener -import kr.genti.core.util.RestartUtil +import kr.genti.core.extension.stringOf +import kr.genti.core.extension.toast +import kr.genti.core.state.UiState +import kr.genti.core.util.RestartUtil.restartApp import kr.genti.presentation.R import kr.genti.presentation.databinding.DialogSettingQuitBinding @@ -39,6 +45,7 @@ class SettingQuitDialog : initReturnBtnListener() initLogoutBtnListener() + observeUserQuitState() } private fun initReturnBtnListener() { @@ -46,15 +53,26 @@ class SettingQuitDialog : } private fun initLogoutBtnListener() { - // TODO : 토큰 설정 이후 탈퇴 설정 binding.btnLogout.setOnSingleClickListener { - lifecycleScope.launch { - delay(500) - RestartUtil.restartApp(binding.root.context, null) - } + viewModel.quitFromKakao() } } + private fun observeUserQuitState() { + viewModel.userDeleteState.flowWithLifecycle(lifecycle).distinctUntilChanged() + .onEach { state -> + when (state) { + is UiState.Success -> { + delay(500) + restartApp(binding.root.context, null) + } + + is UiState.Failure -> toast(stringOf(R.string.error_msg)) + else -> return@onEach + } + }.launchIn(lifecycleScope) + } + override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) requireActivity().window.decorView.rootView.setGusianBlur(null) diff --git a/presentation/src/main/java/kr/genti/presentation/setting/SettingViewModel.kt b/presentation/src/main/java/kr/genti/presentation/setting/SettingViewModel.kt index 3e4b49bf..ac79128c 100644 --- a/presentation/src/main/java/kr/genti/presentation/setting/SettingViewModel.kt +++ b/presentation/src/main/java/kr/genti/presentation/setting/SettingViewModel.kt @@ -1,16 +1,73 @@ package kr.genti.presentation.setting import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.kakao.sdk.user.UserApiClient import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kr.genti.core.state.UiState +import kr.genti.domain.repository.InfoRepository +import kr.genti.domain.repository.UserRepository import javax.inject.Inject @HiltViewModel class SettingViewModel @Inject constructor( - // private val authRepository: AuthRepository + private val infoRepository: InfoRepository, + private val userRepository: UserRepository, ) : ViewModel() { - private fun clearLocalInfo() { - // authRepository.clearLocalPref() + private val _userLogoutState = MutableStateFlow>(UiState.Empty) + val userLogoutState: StateFlow> = _userLogoutState + + private val _userDeleteState = MutableStateFlow>(UiState.Empty) + val userDeleteState: StateFlow> = _userDeleteState + + fun logoutFromKakao() { + _userLogoutState.value = UiState.Loading + UserApiClient.instance.logout { error -> + if (error == null) { + logoutFromServer() + } else { + _userLogoutState.value = UiState.Failure(error.toString()) + } + } + } + + private fun logoutFromServer() { + viewModelScope.launch { + infoRepository.postUserLogout() + .onSuccess { + userRepository.clearInfo() + _userLogoutState.value = UiState.Success(it) + }.onFailure { + _userLogoutState.value = UiState.Failure(it.message.toString()) + } + } + } + + fun quitFromKakao() { + _userDeleteState.value = UiState.Loading + UserApiClient.instance.unlink { error -> + if (error == null) { + quitFromServer() + } else { + _userDeleteState.value = UiState.Failure(error.toString()) + } + } + } + + private fun quitFromServer() { + viewModelScope.launch { + infoRepository.deleteUser() + .onSuccess { + userRepository.clearInfo() + _userDeleteState.value = UiState.Success(it) + }.onFailure { + _userDeleteState.value = UiState.Failure(it.message.toString()) + } + } } } diff --git a/presentation/src/main/res/layout/fragment_define.xml b/presentation/src/main/res/layout/fragment_define.xml index 19c43ee1..02c07627 100644 --- a/presentation/src/main/res/layout/fragment_define.xml +++ b/presentation/src/main/res/layout/fragment_define.xml @@ -88,9 +88,9 @@ android:id="@+id/btn_refresh" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="11dp" + android:layout_marginEnd="21dp" android:background="#D9D9D9" - android:padding="12dp" + android:padding="9dp" android:src="@drawable/ic_refresh" app:layout_constraintBottom_toBottomOf="@id/tv_create_random_example" app:layout_constraintEnd_toEndOf="parent"