From 99cf8dcf2ce006e68d0fd4792fd7ca57f5eea746 Mon Sep 17 00:00:00 2001 From: leeeha Date: Thu, 27 Jun 2024 01:00:25 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[refactor]=20#303=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/nickname/NicknameActivity.kt | 20 ++-- .../nickname/NicknameViewModel.kt | 95 ++++++++----------- .../sopt/winey/util/binding/BindingAdapter.kt | 18 ++-- .../org/go/sopt/winey/util/state/ErrorCode.kt | 9 -- .../go/sopt/winey/util/state/InputError.kt | 12 +++ .../go/sopt/winey/util/state/InputUiState.kt | 2 +- app/src/main/res/values/strings.xml | 1 - 7 files changed, 70 insertions(+), 87 deletions(-) delete mode 100644 app/src/main/java/org/go/sopt/winey/util/state/ErrorCode.kt create mode 100644 app/src/main/java/org/go/sopt/winey/util/state/InputError.kt diff --git a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt index 58a98594..0db4d2ec 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt @@ -19,15 +19,14 @@ import org.go.sopt.winey.domain.repository.DataStoreRepository import org.go.sopt.winey.presentation.main.MainActivity import org.go.sopt.winey.util.amplitude.AmplitudeUtils import org.go.sopt.winey.util.binding.BindingActivity -import org.go.sopt.winey.util.state.ErrorCode import org.go.sopt.winey.util.context.hideKeyboard import org.go.sopt.winey.util.context.snackBar import org.go.sopt.winey.util.context.stringOf +import org.go.sopt.winey.util.state.InputError import org.go.sopt.winey.util.state.InputUiState import org.go.sopt.winey.util.state.UiState import org.json.JSONException import org.json.JSONObject -import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -63,15 +62,12 @@ class NicknameActivity : BindingActivity(R.layout.activ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun afterTextChanged(s: Editable?) {} - // 텍스트가 바뀌면 중복체크 상태 false로 초기화 + // 텍스트가 변할 때마다 중복 체크 상태 갱신 override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { val inputText = s.toString() - - if (inputText.isNotBlank() && inputText != prevText) { + if (inputText != prevText) { viewModel.updateDuplicateCheckState(false) - Timber.d("DUPLICATE CHECK: ${viewModel.duplicateChecked.value}") } - prevText = inputText } }) @@ -80,7 +76,7 @@ class NicknameActivity : BindingActivity(R.layout.activ private fun initDuplicateCheckButtonClickListener() { binding.btnNicknameDuplicateCheck.setOnClickListener { viewModel.apply { - if (checkValidInput()) { + if (validateNickname()) { getNicknameDuplicateCheck() } } @@ -89,15 +85,13 @@ class NicknameActivity : BindingActivity(R.layout.activ private fun initCompleteButtonClickListener() { binding.btnNicknameComplete.setOnClickListener { - // 중복체크 하지 않고 시작하기 버튼 누르면 에러 표시 + if (!viewModel.validateNickname()) return@setOnClickListener + if (!viewModel.duplicateChecked.value) { - viewModel.updateInputUiState( - InputUiState.Failure(ErrorCode.CODE_UNCHECKED_DUPLICATION) - ) + viewModel.showUncheckedDuplicationError() return@setOnClickListener } - // 유효한 닉네임인 경우에만 PATCH 서버통신 진행 if (viewModel.isValidNickname.value) { sendEventToAmplitude() viewModel.patchNickname() diff --git a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt index 85fa1e76..8f80edf0 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt @@ -1,5 +1,6 @@ package org.go.sopt.winey.presentation.nickname +import android.renderscript.ScriptGroup.Input import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -14,98 +15,92 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.go.sopt.winey.data.model.remote.request.RequestPatchNicknameDto import org.go.sopt.winey.domain.repository.AuthRepository -import org.go.sopt.winey.util.state.ErrorCode +import org.go.sopt.winey.util.state.InputError import org.go.sopt.winey.util.state.InputUiState import org.go.sopt.winey.util.state.UiState -import retrofit2.HttpException import timber.log.Timber import javax.inject.Inject @HiltViewModel class NicknameViewModel @Inject constructor( - private val authRepository: AuthRepository + private val authRepository: AuthRepository, ) : ViewModel() { val _nickname = MutableStateFlow("") private val nickname: String get() = _nickname.value - // Why MutableStateFlow -> map 이외의 함수에서도 값을 바꿀 수 있도록 - private val _inputUiState: MutableStateFlow = _nickname.map { checkInputUiState(it) } - .mutableStateIn( - initialValue = InputUiState.Empty, - scope = viewModelScope - ) + 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) } + val isValidNickname: StateFlow = _inputUiState.map { checkNicknameFinally(it) } .stateIn( initialValue = false, scope = viewModelScope, started = SharingStarted.WhileSubscribed(PRODUCE_STOP_TIMEOUT) ) - private fun validateNickname(state: InputUiState) = state == InputUiState.Success - private val _duplicateChecked = MutableStateFlow(false) - val duplicateChecked: StateFlow = _duplicateChecked.asStateFlow() + val duplicateChecked = _duplicateChecked.asStateFlow() private val _patchNicknameState = MutableStateFlow>(UiState.Empty) - val patchNicknameState: StateFlow> = _patchNicknameState.asStateFlow() + val patchNicknameState = _patchNicknameState.asStateFlow() var prevScreenName: String? = null - private fun checkInputUiState(nickname: String): InputUiState { - if (nickname.isBlank()) return InputUiState.Empty - if (!checkLength(nickname)) return InputUiState.Failure(ErrorCode.CODE_INVALID_LENGTH) - if (containsSpaceOrSpecialChar(nickname)) { - return InputUiState.Failure(ErrorCode.CODE_SPACE_SPECIAL_CHAR) + private fun updateInputUiState(nickname: String): InputUiState { + if (containsInvalidChar(nickname)) { + return InputUiState.Failure(InputError.Nickname.INVALID_CHAR) } - return InputUiState.Empty - } - private fun checkLength(nickname: String) = nickname.length in MIN_LENGTH..MAX_LENGTH - - private fun containsSpaceOrSpecialChar(nickname: String) = - !Regex(REGEX_PATTERN).matches(nickname) - - // 액티비티에서 전달 -> XML 바인딩 어댑터에 사용 - fun updatePrevScreenName(intentExtraValue: String?) { - prevScreenName = intentExtraValue + return InputUiState.Empty } - // 중복체크 하지 않고 시작하기 버튼 눌렀을 때 -> Failure 상태로 갱신 - fun updateInputUiState(inputUiState: InputUiState) { - _inputUiState.value = inputUiState - } + private fun containsInvalidChar(nickname: String) = !Regex(REGEX_PATTERN).matches(nickname) - // 액티비티, 뷰모델에서 갱신 - fun updateDuplicateCheckState(checked: Boolean) { - _duplicateChecked.value = checked - } + private fun checkNicknameFinally(state: InputUiState) = state == InputUiState.Success - fun checkValidInput(): Boolean { + fun validateNickname(): Boolean { if (nickname.isBlank()) { - _inputUiState.value = InputUiState.Failure(ErrorCode.CODE_BLANK_INPUT) + _inputUiState.value = InputUiState.Failure(InputError.Nickname.BLANK_INPUT) return false } - if (containsSpaceOrSpecialChar(nickname)) { - _inputUiState.value = InputUiState.Failure(ErrorCode.CODE_SPACE_SPECIAL_CHAR) + if (containsInvalidChar(nickname)) { + _inputUiState.value = InputUiState.Failure(InputError.Nickname.INVALID_CHAR) return false } return true } + fun updatePrevScreenName(intentExtraValue: String?) { + prevScreenName = intentExtraValue + } + + fun updateDuplicateCheckState(checked: Boolean) { + _duplicateChecked.value = checked + } + + fun showUncheckedDuplicationError() { + _inputUiState.value = InputUiState.Failure(InputError.Nickname.UNCHECKED_DUPLICATION) + } + fun getNicknameDuplicateCheck() { viewModelScope.launch { authRepository.getNicknameDuplicateCheck(nickname) .onSuccess { response -> - if (response == null) return@onSuccess - showDuplicateCheckResult(response.isDuplicated) + if (response == null) { + Timber.e("닉네임 중복체크 응답값 null") + return@launch + } updateDuplicateCheckState(true) - Timber.d("DUPLICATE CHECK: ${duplicateChecked.value}") + showDuplicateCheckResult(response.isDuplicated) } .onFailure { t -> Timber.e("${t.message}") @@ -115,7 +110,7 @@ class NicknameViewModel @Inject constructor( private fun showDuplicateCheckResult(isDuplicated: Boolean) { _inputUiState.value = if (isDuplicated) { - InputUiState.Failure(ErrorCode.CODE_DUPLICATED) + InputUiState.Failure(InputError.Nickname.DUPLICATED) } else { InputUiState.Success } @@ -127,24 +122,17 @@ class NicknameViewModel @Inject constructor( authRepository.patchNickname(RequestPatchNicknameDto(nickname)) .onSuccess { response -> - Timber.d("SUCCESS PATCH NICKNAME") _patchNicknameState.value = UiState.Success(response) } .onFailure { t -> - if (t is HttpException) { - Timber.e("HTTP FAIL PATCH NICKNAME: ${t.code()} ${t.message}") - return@onFailure - } - Timber.e("FAIL PATCH NICKNAME: ${t.message}") _patchNicknameState.value = UiState.Failure(t.message.toString()) } } } - // _nickname.map{} Flow -> MutableStateFlow 변환을 위한 확장 함수 private fun Flow.mutableStateIn( initialValue: T, - scope: CoroutineScope + scope: CoroutineScope, ): MutableStateFlow { val flow = MutableStateFlow(initialValue) scope.launch { @@ -154,7 +142,6 @@ class NicknameViewModel @Inject constructor( } 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/org/go/sopt/winey/util/binding/BindingAdapter.kt b/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt index 2c493a60..45e7eba2 100644 --- a/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt +++ b/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt @@ -18,11 +18,11 @@ import org.go.sopt.winey.presentation.model.UserLevel import org.go.sopt.winey.presentation.model.WineyFeedType import org.go.sopt.winey.presentation.nickname.NicknameActivity.Companion.MY_PAGE_SCREEN import org.go.sopt.winey.presentation.nickname.NicknameActivity.Companion.STORY_SCREEN -import org.go.sopt.winey.util.state.ErrorCode.* import org.go.sopt.winey.util.context.colorOf import org.go.sopt.winey.util.context.drawableOf import org.go.sopt.winey.util.context.stringOf import org.go.sopt.winey.util.number.formatAmountNumber +import org.go.sopt.winey.util.state.InputError import org.go.sopt.winey.util.state.InputUiState import org.go.sopt.winey.util.state.InputUiState.* import java.text.DecimalFormat @@ -122,7 +122,7 @@ fun TextView.setUploadContentHelperText(inputUiState: InputUiState) { if (inputUiState is Failure) { visibility = View.VISIBLE - if (inputUiState.code == CODE_INVALID_LENGTH) { + if (inputUiState.error == InputError.Upload) { text = context.stringOf(R.string.upload_content_error_text) } } @@ -167,12 +167,12 @@ fun TextView.setNicknameHelperText(inputUiState: InputUiState) { is Failure -> { visibility = View.VISIBLE - text = when (inputUiState.code) { - CODE_BLANK_INPUT -> context.stringOf(R.string.nickname_blank_input_error) - 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_DUPLICATED -> context.stringOf(R.string.nickname_duplicated_error) + text = when (inputUiState.error) { + InputError.Nickname.BLANK_INPUT -> context.stringOf(R.string.nickname_blank_input_error) + InputError.Nickname.INVALID_CHAR -> context.stringOf(R.string.nickname_space_special_char_error) + InputError.Nickname.UNCHECKED_DUPLICATION -> context.stringOf(R.string.nickname_unchecked_duplication_error) + InputError.Nickname.DUPLICATED -> context.stringOf(R.string.nickname_duplicated_error) + else -> "" } } } @@ -196,7 +196,7 @@ fun TextView.setNicknameHelperTextColor(inputUiState: InputUiState) { fun TextView.setNicknameCounter( prevScreenName: String, inputNicknameLength: Int, - originalNicknameLength: Int + originalNicknameLength: Int, ) { when (prevScreenName) { STORY_SCREEN -> { diff --git a/app/src/main/java/org/go/sopt/winey/util/state/ErrorCode.kt b/app/src/main/java/org/go/sopt/winey/util/state/ErrorCode.kt deleted file mode 100644 index e5f3d2e5..00000000 --- a/app/src/main/java/org/go/sopt/winey/util/state/ErrorCode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.go.sopt.winey.util.state - -enum class ErrorCode { - CODE_BLANK_INPUT, - CODE_SPACE_SPECIAL_CHAR, - CODE_UNCHECKED_DUPLICATION, - CODE_DUPLICATED, - CODE_INVALID_LENGTH -} diff --git a/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt b/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt new file mode 100644 index 00000000..506ba369 --- /dev/null +++ b/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt @@ -0,0 +1,12 @@ +package org.go.sopt.winey.util.state + +sealed interface InputError { + enum class Nickname: InputError { + BLANK_INPUT, + INVALID_CHAR, + UNCHECKED_DUPLICATION, + DUPLICATED + } + + object Upload: InputError +} diff --git a/app/src/main/java/org/go/sopt/winey/util/state/InputUiState.kt b/app/src/main/java/org/go/sopt/winey/util/state/InputUiState.kt index 2352a460..8c10107a 100644 --- a/app/src/main/java/org/go/sopt/winey/util/state/InputUiState.kt +++ b/app/src/main/java/org/go/sopt/winey/util/state/InputUiState.kt @@ -3,5 +3,5 @@ package org.go.sopt.winey.util.state sealed class InputUiState { object Empty : InputUiState() object Success : InputUiState() - data class Failure(val code: ErrorCode) : InputUiState() + data class Failure(val error: InputError) : InputUiState() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d152dce0..aa7c3644 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -229,7 +229,6 @@ 시작하기 입력값이 비어있습니다 :( - 1~8자로 입력해주세요 :( 공백과 특수문자는 사용할 수 없습니다 :( 닉네임 중복확인을 해주세요 :( 중복된 닉네임입니다 :( From af68b3d45c441788b024be598beb167e7d22f065 Mon Sep 17 00:00:00 2001 From: leeeha Date: Thu, 27 Jun 2024 01:01:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[mod]=20#303=20=EC=A0=88=EC=95=BD=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=9E=91=EC=84=B1=20=EC=8B=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../winey/presentation/main/feed/upload/UploadViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/UploadViewModel.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/UploadViewModel.kt index a568e837..44a43f59 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/UploadViewModel.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/UploadViewModel.kt @@ -18,8 +18,8 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto import org.go.sopt.winey.domain.repository.FeedRepository import org.go.sopt.winey.presentation.model.WineyFeedType -import org.go.sopt.winey.util.state.ErrorCode import org.go.sopt.winey.util.multipart.UriToRequestBody +import org.go.sopt.winey.util.state.InputError import org.go.sopt.winey.util.state.InputUiState import org.go.sopt.winey.util.state.UiState import retrofit2.HttpException @@ -94,9 +94,11 @@ class UploadViewModel @Inject constructor( private fun checkInputUiState(content: String): InputUiState { if (content.isBlank()) return InputUiState.Empty + if (!checkContentLength((content))) { - return InputUiState.Failure(ErrorCode.CODE_INVALID_LENGTH) + return InputUiState.Failure(InputError.Upload) } + return InputUiState.Success } From 8539dce41b32c6c1fc7e86de68306ef30eb5c2e8 Mon Sep 17 00:00:00 2001 From: leeeha Date: Thu, 27 Jun 2024 01:02:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[chore]=20#303=20ktlint=20check=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../go/sopt/winey/presentation/nickname/NicknameActivity.kt | 2 -- .../go/sopt/winey/presentation/nickname/NicknameViewModel.kt | 5 ++--- .../java/org/go/sopt/winey/util/binding/BindingAdapter.kt | 2 +- app/src/main/java/org/go/sopt/winey/util/state/InputError.kt | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt index 0db4d2ec..a362d11f 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameActivity.kt @@ -22,8 +22,6 @@ import org.go.sopt.winey.util.binding.BindingActivity import org.go.sopt.winey.util.context.hideKeyboard import org.go.sopt.winey.util.context.snackBar import org.go.sopt.winey.util.context.stringOf -import org.go.sopt.winey.util.state.InputError -import org.go.sopt.winey.util.state.InputUiState import org.go.sopt.winey.util.state.UiState import org.json.JSONException import org.json.JSONObject diff --git a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt index 8f80edf0..03a29e07 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/nickname/NicknameViewModel.kt @@ -1,6 +1,5 @@ package org.go.sopt.winey.presentation.nickname -import android.renderscript.ScriptGroup.Input import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -23,7 +22,7 @@ import javax.inject.Inject @HiltViewModel class NicknameViewModel @Inject constructor( - private val authRepository: AuthRepository, + private val authRepository: AuthRepository ) : ViewModel() { val _nickname = MutableStateFlow("") private val nickname: String get() = _nickname.value @@ -132,7 +131,7 @@ class NicknameViewModel @Inject constructor( private fun Flow.mutableStateIn( initialValue: T, - scope: CoroutineScope, + scope: CoroutineScope ): MutableStateFlow { val flow = MutableStateFlow(initialValue) scope.launch { diff --git a/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt b/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt index 45e7eba2..a9e229f7 100644 --- a/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt +++ b/app/src/main/java/org/go/sopt/winey/util/binding/BindingAdapter.kt @@ -196,7 +196,7 @@ fun TextView.setNicknameHelperTextColor(inputUiState: InputUiState) { fun TextView.setNicknameCounter( prevScreenName: String, inputNicknameLength: Int, - originalNicknameLength: Int, + originalNicknameLength: Int ) { when (prevScreenName) { STORY_SCREEN -> { diff --git a/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt b/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt index 506ba369..e5ca30a8 100644 --- a/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt +++ b/app/src/main/java/org/go/sopt/winey/util/state/InputError.kt @@ -1,12 +1,12 @@ package org.go.sopt.winey.util.state sealed interface InputError { - enum class Nickname: InputError { + enum class Nickname : InputError { BLANK_INPUT, INVALID_CHAR, UNCHECKED_DUPLICATION, DUPLICATED } - object Upload: InputError + object Upload : InputError }