diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index db8c2188..35ebeb7f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,28 +31,34 @@ + + @@ -61,6 +67,7 @@ android:exported="true"> + diff --git a/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseGetNicknameDuplicateCheckDto.kt b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseGetNicknameDuplicateCheckDto.kt new file mode 100644 index 00000000..b13c3986 --- /dev/null +++ b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseGetNicknameDuplicateCheckDto.kt @@ -0,0 +1,10 @@ +package com.android.go.sopt.winey.data.model.remote.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetNicknameDuplicateCheckDto( + @SerialName("isDuplicated") + val isDuplicated: Boolean +) diff --git a/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt index 22d2f353..702c365a 100644 --- a/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.android.go.sopt.winey.data.repository import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto +import com.android.go.sopt.winey.data.model.remote.response.ResponseGetNicknameDuplicateCheckDto import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto @@ -79,4 +80,9 @@ class AuthRepositoryImpl @Inject constructor( runCatching { authDataSource.postReIssueToken(refreshToken).data } + + override suspend fun getNicknameDuplicateCheck(nickname: String): Result = + runCatching { + authDataSource.getNicknameDuplicateCheck(nickname).data + } } diff --git a/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt b/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt index c64493e7..20a21b58 100644 --- a/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt +++ b/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt @@ -4,6 +4,7 @@ import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto import com.android.go.sopt.winey.data.model.remote.response.ResponseCreateGoalDto +import com.android.go.sopt.winey.data.model.remote.response.ResponseGetNicknameDuplicateCheckDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetRecommendListDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetUserDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetWineyFeedListDto @@ -26,6 +27,10 @@ import retrofit2.http.Path import retrofit2.http.Query interface AuthService { + /** + * + */ + @GET("user") suspend fun getUser(): BaseResponse @@ -77,4 +82,9 @@ interface AuthService { suspend fun postReIssueToken( @Header("refreshToken") refreshToken: String ): BaseResponse + + @GET("user/nickname/is-exist") + suspend fun getNicknameDuplicateCheck( + @Query("nickname") nickname: String + ): BaseResponse } diff --git a/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt b/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt index 532fb32a..7d963514 100644 --- a/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt +++ b/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt @@ -4,6 +4,7 @@ import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto import com.android.go.sopt.winey.data.model.remote.response.ResponseCreateGoalDto +import com.android.go.sopt.winey.data.model.remote.response.ResponseGetNicknameDuplicateCheckDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetRecommendListDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetUserDto import com.android.go.sopt.winey.data.model.remote.response.ResponseGetWineyFeedListDto @@ -59,4 +60,7 @@ class AuthDataSource @Inject constructor( refreshToken: String ): BaseResponse = authService.postReIssueToken(refreshToken) + + suspend fun getNicknameDuplicateCheck(nickname: String): BaseResponse = + authService.getNicknameDuplicateCheck(nickname) } diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt b/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt index 951abdf3..5be4336b 100644 --- a/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt @@ -3,6 +3,7 @@ package com.android.go.sopt.winey.domain.repository import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto +import com.android.go.sopt.winey.data.model.remote.response.ResponseGetNicknameDuplicateCheckDto import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto @@ -39,4 +40,6 @@ interface AuthRepository { ): Result suspend fun postReIssueToken(refreshToken: String): Result + + suspend fun getNicknameDuplicateCheck(nickname: String): Result } diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainActivity.kt index 69c72cef..008a9e8b 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainActivity.kt @@ -36,23 +36,22 @@ class MainActivity : BindingActivity(R.layout.activity_main } } - fun syncBottomNavigationSelection() { + private fun syncBottomNavigationSelection() { supportFragmentManager.addOnBackStackChangedListener { syncBottomNavigation() } } - fun syncBottomNavigation() { + private fun syncBottomNavigation() { val currentFragment = supportFragmentManager.findFragmentById(R.id.fcv_main) when (currentFragment) { is WineyFeedFragment -> binding.bnvMain.selectedItemId = R.id.menu_feed is RecommendFragment -> binding.bnvMain.selectedItemId = R.id.menu_recommend is MyPageFragment -> binding.bnvMain.selectedItemId = R.id.menu_mypage - // 다른 프래그먼트도 추가가능 } } - inline fun navigateTo() { + private inline fun navigateTo() { supportFragmentManager.commit { replace(R.id.fcv_main, T::class.simpleName) } diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/upload/amount/AmountFragment.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/upload/amount/AmountFragment.kt index 5b0984a6..5eacf827 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/upload/amount/AmountFragment.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/upload/amount/AmountFragment.kt @@ -11,12 +11,12 @@ import androidx.lifecycle.flowWithLifecycle import com.android.go.sopt.winey.R import com.android.go.sopt.winey.databinding.FragmentAmountBinding import com.android.go.sopt.winey.presentation.main.feed.upload.loading.LoadingActivity -import com.android.go.sopt.winey.util.multipart.UriToRequestBody import com.android.go.sopt.winey.util.binding.BindingFragment import com.android.go.sopt.winey.util.context.hideKeyboard import com.android.go.sopt.winey.util.fragment.snackBar import com.android.go.sopt.winey.util.fragment.viewLifeCycle import com.android.go.sopt.winey.util.fragment.viewLifeCycleScope +import com.android.go.sopt.winey.util.multipart.UriToRequestBody import com.android.go.sopt.winey.util.view.UiState import com.android.go.sopt.winey.util.view.setOnSingleClickListener import dagger.hilt.android.AndroidEntryPoint diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/KakaoLoginCallback.kt similarity index 97% rename from app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt rename to app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/KakaoLoginCallback.kt index aa22fab8..04421b56 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/KakaoLoginCallback.kt @@ -1,4 +1,4 @@ -package com.android.go.sopt.winey.presentation.onboarding +package com.android.go.sopt.winey.presentation.onboarding.login import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.AuthErrorCause diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginActivity.kt similarity index 69% rename from app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt rename to app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginActivity.kt index 253a04c3..b2e22b43 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginActivity.kt @@ -1,5 +1,6 @@ -package com.android.go.sopt.winey.presentation.onboarding +package com.android.go.sopt.winey.presentation.onboarding.login +import android.app.Activity import android.content.Intent import android.os.Bundle import androidx.activity.viewModels @@ -8,12 +9,14 @@ import androidx.lifecycle.lifecycleScope import com.android.go.sopt.winey.R import com.android.go.sopt.winey.databinding.ActivityLoginBinding import com.android.go.sopt.winey.presentation.main.MainActivity +import com.android.go.sopt.winey.presentation.onboarding.nickname.NicknameActivity import com.android.go.sopt.winey.util.binding.BindingActivity import com.android.go.sopt.winey.util.context.snackBar import com.android.go.sopt.winey.util.view.UiState import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import timber.log.Timber @AndroidEntryPoint class LoginActivity : @@ -40,15 +43,15 @@ class LoginActivity : } is UiState.Success -> { + Timber.e("${state.data?.userId}") + Timber.e("${state.data?.accessToken}") + Timber.e("${state.data?.refreshToken}") + Timber.e("${state.data?.isRegistered}") + if (state.data?.isRegistered == true) { - val intent = Intent(this@LoginActivity, MainActivity::class.java) - startActivity(intent) - finish() + navigateTo() } else { - //TODO : isRegistered false일경우 닉네임 설정화면으로 - val intent = Intent(this@LoginActivity, MainActivity::class.java) - startActivity(intent) - finish() + navigateTo() } } @@ -61,4 +64,11 @@ class LoginActivity : } }.launchIn(lifecycleScope) } + + private inline fun navigateTo() { + Intent(this@LoginActivity, T::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(this) + } + } } diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginViewModel.kt similarity index 98% rename from app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt rename to app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginViewModel.kt index bff76e53..c626d88d 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/login/LoginViewModel.kt @@ -1,4 +1,4 @@ -package com.android.go.sopt.winey.presentation.onboarding +package com.android.go.sopt.winey.presentation.onboarding.login import android.content.Context import androidx.lifecycle.ViewModel diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameActivity.kt new file mode 100644 index 00000000..3169aa2c --- /dev/null +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameActivity.kt @@ -0,0 +1,74 @@ +package com.android.go.sopt.winey.presentation.onboarding.nickname + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import androidx.activity.viewModels +import com.android.go.sopt.winey.R +import com.android.go.sopt.winey.databinding.ActivityNicknameBinding +import com.android.go.sopt.winey.presentation.main.MainActivity +import com.android.go.sopt.winey.util.binding.BindingActivity +import com.android.go.sopt.winey.util.context.hideKeyboard +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class NicknameActivity : BindingActivity(R.layout.activity_nickname) { + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding.vm = viewModel + + initRootLayoutClickListener() + initEditTextWatcher() + initDuplicateCheckButtonClickListener() + initCompleteButtonClickListener() + } + + private fun initEditTextWatcher() { + var prevText = "" + binding.etNickname.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun afterTextChanged(s: Editable?) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val inputText = s.toString() + viewModel.updateTextChangedState(inputText.isNotBlank() && inputText != prevText) + prevText = inputText + } + }) + } + + private fun initDuplicateCheckButtonClickListener() { + binding.btnNicknameDuplicateCheck.setOnClickListener { + if (viewModel.isTextChanged.value) { + viewModel.apply { + updateDuplicateCheckButtonState(true) + getNicknameDuplicateCheck() + } + } + } + } + + private fun initCompleteButtonClickListener() { + binding.btnNicknameComplete.setOnClickListener { + // todo: 서버에 닉네임 PATCH + navigateTo() + } + } + + private fun initRootLayoutClickListener() { + binding.root.setOnClickListener { + hideKeyboard(binding.root) + binding.etNickname.clearFocus() + } + } + + private inline fun navigateTo() { + Intent(this@NicknameActivity, T::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(this) + } + } +} diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameViewModel.kt new file mode 100644 index 00000000..51a43632 --- /dev/null +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/nickname/NicknameViewModel.kt @@ -0,0 +1,166 @@ +package com.android.go.sopt.winey.presentation.onboarding.nickname + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.android.go.sopt.winey.domain.repository.AuthRepository +import com.android.go.sopt.winey.util.code.NicknameErrorCode +import com.android.go.sopt.winey.util.code.NicknameErrorCode.CODE_DUPLICATE +import com.android.go.sopt.winey.util.code.NicknameErrorCode.CODE_INVALID_LENGTH +import com.android.go.sopt.winey.util.code.NicknameErrorCode.CODE_SPACE_SPECIAL_CHAR +import com.android.go.sopt.winey.util.code.NicknameErrorCode.CODE_UNCHECKED_DUPLICATION +import com.android.go.sopt.winey.util.view.InputUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class NicknameViewModel @Inject constructor( + private val authRepository: AuthRepository +) : ViewModel() { + val _nickname = MutableStateFlow("") + val nickname: String get() = _nickname.value + + private val _inputUiState: MutableStateFlow = + _nickname.map { updateInputUiState(it) } + .mutableStateIn( + initialValue = InputUiState.Empty, + scope = viewModelScope + ) + + val inputUiState: StateFlow = _inputUiState.asStateFlow() + + val isValidNickname: StateFlow = _inputUiState.map { validateNickname(it) } + .stateIn( + initialValue = false, + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(PRODUCE_STOP_TIMEOUT) + ) + + private fun validateNickname(state: InputUiState) = state == InputUiState.Success + + private val _isTextChanged = MutableStateFlow(false) + val isTextChanged: StateFlow = _isTextChanged.asStateFlow() + + private val _isCheckBtnClicked = MutableStateFlow(false) + val isCheckBtnClicked: StateFlow = _isCheckBtnClicked.asStateFlow() + + private var prevCheckResult: Pair? = null + + fun getNicknameDuplicateCheck() { + viewModelScope.launch { + authRepository.getNicknameDuplicateCheck(nickname) + .onSuccess { response -> + if (response == null) return@onSuccess + + response.isDuplicated.let { + Timber.e("SUCCESS GET DUPLICATION CHECK: $it") + updateDuplicateCheckState(it) + saveDuplicateCheckState(it) + } + } + .onFailure { t -> + Timber.e("${t.message}") + } + } + } + + private fun updateDuplicateCheckState(isDuplicated: Boolean) { + _inputUiState.value = if (isDuplicated) { + InputUiState.Failure(NicknameErrorCode.CODE_DUPLICATE) + } else { + InputUiState.Success + } + } + + // 서버통신 할 때마다 현재 닉네임과 중복여부를 저장한다. + private fun saveDuplicateCheckState(isDuplicated: Boolean) { + prevCheckResult = Pair(nickname, isDuplicated) + } + + private fun updateInputUiState(nickname: String): InputUiState { + if (nickname.isEmpty()) return InputUiState.Empty + if (!checkLength(nickname)) return InputUiState.Failure(CODE_INVALID_LENGTH) + if (containsSpaceOrSpecialChar(nickname)) { + return InputUiState.Failure( + CODE_SPACE_SPECIAL_CHAR + ) + } + + // 텍스트가 바뀌었는데 중복체크 버튼을 누르지 않은 경우 + if (isTextChanged.value && !isCheckBtnClicked.value) { + return comparePrevCheckResult() + } + + return InputUiState.Empty + } + + private fun comparePrevCheckResult(): InputUiState { + // 이전에 서버통신 한 결과가 없는 경우 + if (prevCheckResult == null) { + Timber.d("CASE 1") + return InputUiState.Failure(CODE_UNCHECKED_DUPLICATION) + } + + // 현재 입력값이 이전에 서버통신 했던 닉네임과 일치하는 경우 + if (nickname == prevCheckResult?.first) { + // 그때 당시의 서버통신 결과 그대로 반환 + if (prevCheckResult?.second == true) { + Timber.d("CASE 2") + return InputUiState.Failure(CODE_DUPLICATE) + } + + Timber.d("CASE 3") + return InputUiState.Success + } + + // 이전에 서버통신한 결과가 있지만, 입력값이 다른 경우 + Timber.d("CASE 4") + return InputUiState.Failure(CODE_UNCHECKED_DUPLICATION) + } + + private fun checkLength(nickname: String) = nickname.length in MIN_LENGTH..MAX_LENGTH + + private fun containsSpaceOrSpecialChar(nickname: String) = + !Regex(REGEX_PATTERN).matches(nickname) + + fun updateTextChangedState(state: Boolean) { // updated in Activity + _isTextChanged.value = state + } + + fun updateDuplicateCheckButtonState(state: Boolean) { // updated in Activity + _isCheckBtnClicked.value = state + initDuplicateCheckButtonState() + } + + private fun initDuplicateCheckButtonState() { + _isCheckBtnClicked.value = false + } + + // _nickname.map{} Flow -> MutableStateFlow 변환을 위한 확장 함수 + private fun Flow.mutableStateIn( + initialValue: T, + scope: CoroutineScope + ): MutableStateFlow { + val flow = MutableStateFlow(initialValue) + scope.launch { + this@mutableStateIn.collect(flow) + } + return flow + } + + companion object { + private const val MIN_LENGTH = 1 + const val MAX_LENGTH = 8 + private const val REGEX_PATTERN = "^[0-9|a-z|A-Z|ㄱ-ㅎ|ㅏ-ㅣ|가-힣]*$" + private const val PRODUCE_STOP_TIMEOUT = 5000L + } +} diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt index 99fd9617..21f19002 100644 --- a/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt +++ b/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt @@ -1,5 +1,6 @@ package com.android.go.sopt.winey.presentation.splash +import android.app.Activity import android.content.Intent import android.os.Bundle import androidx.lifecycle.lifecycleScope @@ -7,7 +8,8 @@ import com.android.go.sopt.winey.R import com.android.go.sopt.winey.databinding.ActivitySplashBinding import com.android.go.sopt.winey.domain.repository.DataStoreRepository import com.android.go.sopt.winey.presentation.main.MainActivity -import com.android.go.sopt.winey.presentation.onboarding.LoginActivity +import com.android.go.sopt.winey.presentation.onboarding.login.LoginActivity +import com.android.go.sopt.winey.presentation.onboarding.nickname.NicknameActivity import com.android.go.sopt.winey.util.binding.BindingActivity import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay @@ -16,6 +18,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import javax.inject.Inject +/** + * Splash -> Onboarding -> Login -> 여기서 서버 오류 발생 -> Nickname -> Main + * -> 자동 로그인 -> Main + * */ @AndroidEntryPoint class SplashActivity : BindingActivity(R.layout.activity_splash) { @Inject @@ -25,33 +31,25 @@ class SplashActivity : BindingActivity(R.layout.activity_ lifecycleScope.launch { delay(DELAY_TIME) - checkAutoLogin() + //checkAutoLogin() + navigateTo() } } private fun checkAutoLogin() { val accessToken = runBlocking { dataStoreRepository.getAccessToken().firstOrNull() } if (accessToken.isNullOrBlank()) { - navigateToOnBoardingScreen() + navigateTo() } else { - navigateToMainScreen() + navigateTo() } } - private fun navigateToOnBoardingScreen() { - Intent(this@SplashActivity, LoginActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + private inline fun navigateTo() { + Intent(this@SplashActivity, T::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(this) } - finish() - } - - private fun navigateToMainScreen() { - Intent(this@SplashActivity, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(this) - } - finish() } companion object { diff --git a/app/src/main/java/com/android/go/sopt/winey/util/binding/BindingAdapter.kt b/app/src/main/java/com/android/go/sopt/winey/util/binding/BindingAdapter.kt index 41ea322f..d22f7bd1 100644 --- a/app/src/main/java/com/android/go/sopt/winey/util/binding/BindingAdapter.kt +++ b/app/src/main/java/com/android/go/sopt/winey/util/binding/BindingAdapter.kt @@ -2,6 +2,8 @@ package com.android.go.sopt.winey.util.binding import android.graphics.drawable.Drawable import android.net.Uri +import android.view.View +import android.widget.EditText import android.widget.ImageView import android.widget.ImageView.ScaleType import android.widget.TextView @@ -9,6 +11,12 @@ import androidx.databinding.BindingAdapter import coil.load import coil.transform.RoundedCornersTransformation import com.android.go.sopt.winey.R +import com.android.go.sopt.winey.util.code.NicknameErrorCode.* +import com.android.go.sopt.winey.util.context.colorOf +import com.android.go.sopt.winey.util.context.drawableOf +import com.android.go.sopt.winey.util.context.stringOf +import com.android.go.sopt.winey.util.view.InputUiState +import com.android.go.sopt.winey.util.view.InputUiState.* import java.text.DecimalFormat @BindingAdapter("likedAmount") @@ -53,3 +61,45 @@ fun ImageView.setRoundedImage(imageUri: Uri?, drawable: Drawable) { transformations(RoundedCornersTransformation(10f)) } } + +@BindingAdapter("setBackground") +fun EditText.setBackground(inputUiState: InputUiState) { + background = when (inputUiState) { + is Empty -> context.drawableOf(R.drawable.sel_nickname_edittext_focus_color) + is Success -> context.drawableOf(R.drawable.shape_blue_line_5_rect) + is Failure -> context.drawableOf(R.drawable.shape_red_line_5_rect) + } +} + +@BindingAdapter("setHelperText") +fun TextView.setHelperText(inputUiState: InputUiState) { + when (inputUiState) { + is Empty -> { + visibility = View.INVISIBLE + } + + is Success -> { + visibility = View.VISIBLE + text = context.stringOf(R.string.nickname_valid) + } + + is Failure -> { + visibility = View.VISIBLE + text = when (inputUiState.code) { + CODE_INVALID_LENGTH -> context.stringOf(R.string.nickname_invalid_length_error) + CODE_SPACE_SPECIAL_CHAR -> context.stringOf(R.string.nickname_space_special_char_error) + CODE_UNCHECKED_DUPLICATION -> context.stringOf(R.string.nickname_unchecked_duplication_error) + CODE_DUPLICATE -> context.stringOf(R.string.nickname_duplicate_error) + } + } + } +} + +@BindingAdapter("setHelperTextColor") +fun TextView.setHelperTextColor(inputUiState: InputUiState) { + when (inputUiState) { + is Empty -> setTextColor(context.colorOf(R.color.gray_200)) + is Success -> setTextColor(context.colorOf(R.color.blue_500)) + is Failure -> setTextColor(context.colorOf(R.color.red_500)) + } +} diff --git a/app/src/main/java/com/android/go/sopt/winey/util/code/NicknameErrorCode.kt b/app/src/main/java/com/android/go/sopt/winey/util/code/NicknameErrorCode.kt new file mode 100644 index 00000000..d480bae1 --- /dev/null +++ b/app/src/main/java/com/android/go/sopt/winey/util/code/NicknameErrorCode.kt @@ -0,0 +1,8 @@ +package com.android.go.sopt.winey.util.code + +enum class NicknameErrorCode { + CODE_INVALID_LENGTH, + CODE_SPACE_SPECIAL_CHAR, + CODE_UNCHECKED_DUPLICATION, + CODE_DUPLICATE +} diff --git a/app/src/main/java/com/android/go/sopt/winey/util/view/InputUiState.kt b/app/src/main/java/com/android/go/sopt/winey/util/view/InputUiState.kt new file mode 100644 index 00000000..47de4e72 --- /dev/null +++ b/app/src/main/java/com/android/go/sopt/winey/util/view/InputUiState.kt @@ -0,0 +1,9 @@ +package com.android.go.sopt.winey.util.view + +import com.android.go.sopt.winey.util.code.NicknameErrorCode + +sealed class InputUiState { + object Empty : InputUiState() + object Success : InputUiState() + data class Failure(val code: NicknameErrorCode) : InputUiState() +} diff --git a/app/src/main/res/drawable/sel_nickname_edittext_focus_color.xml b/app/src/main/res/drawable/sel_nickname_edittext_focus_color.xml new file mode 100644 index 00000000..3828bbf9 --- /dev/null +++ b/app/src/main/res/drawable/sel_nickname_edittext_focus_color.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/sel_upload_next_btn_bg_color.xml b/app/src/main/res/drawable/sel_upload_nickname_btn_color.xml similarity index 100% rename from app/src/main/res/drawable/sel_upload_next_btn_bg_color.xml rename to app/src/main/res/drawable/sel_upload_nickname_btn_color.xml diff --git a/app/src/main/res/drawable/sel_upload_next_btn_text_color.xml b/app/src/main/res/drawable/sel_upload_nickname_btn_text_color.xml similarity index 100% rename from app/src/main/res/drawable/sel_upload_next_btn_text_color.xml rename to app/src/main/res/drawable/sel_upload_nickname_btn_text_color.xml diff --git a/app/src/main/res/drawable/shape_blue_line_5_rect.xml b/app/src/main/res/drawable/shape_blue_line_5_rect.xml new file mode 100644 index 00000000..8de00935 --- /dev/null +++ b/app/src/main/res/drawable/shape_blue_line_5_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape_gray_line_5_rect.xml b/app/src/main/res/drawable/shape_gray_line_5_rect.xml new file mode 100644 index 00000000..fd9d6378 --- /dev/null +++ b/app/src/main/res/drawable/shape_gray_line_5_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape_purple_line_5_rect.xml b/app/src/main/res/drawable/shape_purple_line_5_rect.xml new file mode 100644 index 00000000..5d4874c1 --- /dev/null +++ b/app/src/main/res/drawable/shape_purple_line_5_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape_red_line_5_rect.xml b/app/src/main/res/drawable/shape_red_line_5_rect.xml new file mode 100644 index 00000000..3df03090 --- /dev/null +++ b/app/src/main/res/drawable/shape_red_line_5_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_nickname.xml b/app/src/main/res/layout/activity_nickname.xml new file mode 100644 index 00000000..813f267a --- /dev/null +++ b/app/src/main/res/layout/activity_nickname.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +