Skip to content

Commit

Permalink
[fix] #197 닉네임 중복체크 버튼을 눌렀을 때 한번 더 유효한 값인지 검사하도록 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
leeeha committed Sep 16, 2023
1 parent 2b63b71 commit 34caf44
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class UploadViewModel @Inject constructor(
/** Content Fragment */
val _content = MutableStateFlow("")
val content: String get() = _content.value
val inputUiState: StateFlow<InputUiState> = _content.map { updateInputUiState(it) }

val inputUiState: StateFlow<InputUiState> = _content.map { checkInputUiState(it) }
.stateIn(
initialValue = InputUiState.Empty,
scope = viewModelScope,
Expand All @@ -62,7 +63,7 @@ class UploadViewModel @Inject constructor(

private fun validateContent(state: InputUiState) = state == InputUiState.Success

private fun updateInputUiState(content: String): InputUiState {
private fun checkInputUiState(content: String): InputUiState {
if (content.isBlank()) return InputUiState.Empty
if (!checkContentLength((content))) {
return InputUiState.Failure(ErrorCode.CODE_INVALID_LENGTH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.go.sopt.winey.util.view.InputUiState
import org.go.sopt.winey.util.view.UiState
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -43,7 +44,6 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
amplitudeUtils.logEvent("view_set_nickname")

binding.vm = viewModel
viewModel.updatePrevScreenName(prevScreenName)

Expand All @@ -57,18 +57,6 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
initPatchNicknameStateObserver()
}

private fun sendEventToAmplitude() {
val eventProperties = JSONObject()
try {
eventProperties.put("button_name", "nickname_next_button")
.put("paging_number", 1)
} catch (e: JSONException) {
System.err.println("Invalid JSON")
e.printStackTrace()
}
amplitudeUtils.logEvent("click_button", eventProperties)
}

private fun initEditTextWatcher() {
var prevText = ""
binding.etNickname.addTextChangedListener(object : TextWatcher {
Expand All @@ -78,31 +66,38 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
// 텍스트가 바뀌면 중복체크 상태 false로 초기화
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val inputText = s.toString()

if (inputText.isNotBlank() && inputText != prevText) {
viewModel.updateDuplicateCheckState(false)
Timber.d("DUPLICATE CHECK: ${viewModel.duplicateChecked.value}")
}

prevText = inputText
}
})
}

private fun initDuplicateCheckButtonClickListener() {
binding.btnNicknameDuplicateCheck.setOnClickListener {
viewModel.getNicknameDuplicateCheck()
viewModel.apply {
if (checkValidInput()) {
getNicknameDuplicateCheck()
}
}
}
}

private fun initCompleteButtonClickListener() {
binding.btnNicknameComplete.setOnClickListener {
// 중복체크를 하지 않은 상태에서 완료 버튼을 클릭하면 에러 표시
if (!viewModel.isDuplicateChecked.value) {
// 중복체크 하지 않고 시작하기 버튼 누르면 에러 표시
if (!viewModel.duplicateChecked.value) {
viewModel.updateInputUiState(
InputUiState.Failure(ErrorCode.CODE_UNCHECKED_DUPLICATION)
)
return@setOnClickListener
}

// 서버통신 결과 중복되지 않은 닉네임인 경우에만 PATCH 서버통신 진행
// 유효한 닉네임인 경우에만 PATCH 서버통신 진행
if (viewModel.isValidNickname.value) {
sendEventToAmplitude()
viewModel.patchNickname()
Expand Down Expand Up @@ -133,13 +128,13 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
}

private fun switchEditTextHint() {
lifecycleScope.launch {
when (prevScreenName) {
STORY_SCREEN -> {
binding.etNickname.hint = stringOf(R.string.nickname_default_hint)
}
when (prevScreenName) {
STORY_SCREEN -> {
binding.etNickname.hint = stringOf(R.string.nickname_default_hint)
}

MY_PAGE_SCREEN -> {
MY_PAGE_SCREEN -> {
lifecycleScope.launch {
val user = dataStoreRepository.getUserInfo().first() ?: return@launch
binding.etNickname.hint = user.nickname
binding.originalNicknameLength = user.nickname.length
Expand Down Expand Up @@ -168,6 +163,18 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
}
}

private fun sendEventToAmplitude() {
val eventProperties = JSONObject()
try {
eventProperties.put("button_name", "nickname_next_button")
.put("paging_number", 1)
} catch (e: JSONException) {
System.err.println("Invalid JSON")
e.printStackTrace()
}
amplitudeUtils.logEvent("click_button", eventProperties)
}

companion object {
private const val EXTRA_KEY = "PREV_SCREEN_NAME"
const val MY_PAGE_SCREEN = "MyPageFragment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ class NicknameViewModel @Inject constructor(
private val authRepository: AuthRepository
) : ViewModel() {
val _nickname = MutableStateFlow("")
val nickname: String get() = _nickname.value

private val _inputUiState: MutableStateFlow<InputUiState> =
_nickname.map { checkInputUiState(it) }
.mutableStateIn(
initialValue = InputUiState.Empty,
scope = viewModelScope
)
private val nickname: String get() = _nickname.value

// Why MutableStateFlow -> map 이외의 함수에서도 값을 바꿀 수 있도록
private val _inputUiState: MutableStateFlow<InputUiState> = _nickname.map { checkInputUiState(it) }
.mutableStateIn(
initialValue = InputUiState.Empty,
scope = viewModelScope
)

val inputUiState: StateFlow<InputUiState> = _inputUiState.asStateFlow()

val isValidNickname: StateFlow<Boolean> = _inputUiState.map { validateNickname(it) }
Expand All @@ -42,61 +43,69 @@ class NicknameViewModel @Inject constructor(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(PRODUCE_STOP_TIMEOUT)
)

private fun validateNickname(state: InputUiState) = state == InputUiState.Success

private val _isDuplicateChecked = MutableStateFlow(false)
val isDuplicateChecked: StateFlow<Boolean> = _isDuplicateChecked.asStateFlow()
private val _duplicateChecked = MutableStateFlow(false)
val duplicateChecked: StateFlow<Boolean> = _duplicateChecked.asStateFlow()

private val _patchNicknameState = MutableStateFlow<UiState<Unit>>(UiState.Empty)
val patchNicknameState: StateFlow<UiState<Unit>> = _patchNicknameState.asStateFlow()

private var prevCheckResult: Pair<String, Boolean>? = null

var prevScreenName: String? = null

fun updatePrevScreenName(name: String?) {
prevScreenName = name
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)
}
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
}

// 중복체크 하지 않고 시작하기 버튼 눌렀을 때 -> Failure 상태로 갱신
fun updateInputUiState(inputUiState: InputUiState) {
_inputUiState.value = inputUiState
}

// 액티비티, 뷰모델에서 갱신
fun updateDuplicateCheckState(checked: Boolean) {
_isDuplicateChecked.value = checked
_duplicateChecked.value = checked
}

fun patchNickname() {
viewModelScope.launch {
_patchNicknameState.value = UiState.Loading
fun checkValidInput(): Boolean {
if (nickname.isBlank()) {
_inputUiState.value = InputUiState.Failure(ErrorCode.CODE_BLANK_INPUT)
return false
}

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())
}
if (containsSpaceOrSpecialChar(nickname)) {
_inputUiState.value = InputUiState.Failure(ErrorCode.CODE_SPACE_SPECIAL_CHAR)
return false
}

return true
}

fun getNicknameDuplicateCheck() {
viewModelScope.launch {
authRepository.getNicknameDuplicateCheck(nickname)
.onSuccess { response ->
if (response == null) return@onSuccess
showDuplicateCheckResult(response.isDuplicated)

response.isDuplicated.let {
showDuplicateCheckResult(it)
saveDuplicateCheckResult(it)
}
updateDuplicateCheckState(true)
Timber.d("DUPLICATE CHECK: ${duplicateChecked.value}")
}
.onFailure { t ->
Timber.e("${t.message}")
Expand All @@ -106,30 +115,32 @@ class NicknameViewModel @Inject constructor(

private fun showDuplicateCheckResult(isDuplicated: Boolean) {
_inputUiState.value = if (isDuplicated) {
InputUiState.Failure(ErrorCode.CODE_DUPLICATE)
InputUiState.Failure(ErrorCode.CODE_DUPLICATED)
} else {
InputUiState.Success
}
}

private fun saveDuplicateCheckResult(isDuplicated: Boolean) {
prevCheckResult = Pair(nickname, isDuplicated)
}
fun patchNickname() {
viewModelScope.launch {
_patchNicknameState.value = UiState.Loading

private fun checkInputUiState(nickname: String): InputUiState {
if (nickname.isEmpty()) return InputUiState.Empty
if (!checkLength(nickname)) return InputUiState.Failure(ErrorCode.CODE_INVALID_LENGTH)
if (containsSpaceOrSpecialChar(nickname)) {
return InputUiState.Failure(ErrorCode.CODE_SPACE_SPECIAL_CHAR)
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())
}
}
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)

// _nickname.map{} Flow -> MutableStateFlow 변환을 위한 확장 함수
private fun <T> Flow<T>.mutableStateIn(
initialValue: T,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import de.hdodenhof.circleimageview.CircleImageView
import org.go.sopt.winey.R
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.code.ErrorCode
import org.go.sopt.winey.util.code.ErrorCode.*
import org.go.sopt.winey.util.context.colorOf
import org.go.sopt.winey.util.context.drawableOf
Expand Down Expand Up @@ -84,7 +83,7 @@ fun TextView.setUploadContentHelperText(inputUiState: InputUiState) {

if (inputUiState is Failure) {
visibility = View.VISIBLE
if (inputUiState.code == ErrorCode.CODE_INVALID_LENGTH) {
if (inputUiState.code == CODE_INVALID_LENGTH) {
text = context.stringOf(R.string.upload_content_error_text)
}
}
Expand Down Expand Up @@ -114,10 +113,11 @@ 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_DUPLICATE -> context.stringOf(R.string.nickname_duplicate_error)
CODE_DUPLICATED -> context.stringOf(R.string.nickname_duplicated_error)
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/org/go/sopt/winey/util/code/ErrorCode.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.go.sopt.winey.util.code

enum class ErrorCode {
CODE_INVALID_LENGTH,
CODE_BLANK_INPUT,
CODE_SPACE_SPECIAL_CHAR,
CODE_UNCHECKED_DUPLICATION,
CODE_DUPLICATE
CODE_DUPLICATED,
CODE_INVALID_LENGTH
}
4 changes: 3 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,12 @@
<string name="nickname_duplicate_check">중복확인</string>
<string name="nickname_update_complete_btn_text">확인</string>
<string name="nickname_start_btn_text">시작하기</string>

<string name="nickname_blank_input_error">입력값이 비어있습니다 :(</string>
<string name="nickname_invalid_length_error">1~8자로 입력해주세요 :(</string>
<string name="nickname_space_special_char_error">공백과 특수문자는 사용할 수 없습니다 :(</string>
<string name="nickname_unchecked_duplication_error">닉네임 중복확인을 해주세요 :(</string>
<string name="nickname_duplicate_error">중복된 닉네임입니다 :(</string>
<string name="nickname_duplicated_error">중복된 닉네임입니다 :(</string>
<string name="nickname_valid">사용 가능한 닉네임입니다 :)</string>

<!-- login -->
Expand Down

0 comments on commit 34caf44

Please sign in to comment.