Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mod] 온보딩 / 1차 릴리즈 QA 피드백 반영 #155

Merged
merged 12 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
820c25c
[mod] #150 사용 가이드 화면에서 손가락으로도 스와이프 가능하도록 변경
leeeha Aug 24, 2023
e0d7ee6
[mod] #150 로티 애니메이션 높이 30dp 축소
leeeha Aug 24, 2023
14a6825
[fix] #150 첫번째 스토리 화면에서 위니국 말풍선 텍스트 개행되는 이슈 해결
leeeha Aug 25, 2023
6637c92
[fix] #150 말풍선과 텍스트가 로티 애니메이션 배경에 가려지는 이슈 해결
leeeha Aug 25, 2023
ae32d81
[ui] #150 스토리 화면 텍스트 컬러 수정
leeeha Aug 25, 2023
18587de
[feat] #150 세계관 스토리텔링 화면에서 메인 피드로 건너뛰는 기능 구현
leeeha Aug 25, 2023
41b9991
[fix] #150 닉네임 중복확인 버튼 개행되는 이슈 해결 (Linear 대신 Constraint 레이아웃 사용)
leeeha Aug 25, 2023
3a1d1c5
[mod] #150 바인딩 어댑터 사용해서 닉네임 close 버튼, 타이틀 텍스트, 버튼 텍스트 조정하도록 변경
leeeha Aug 25, 2023
9d8f3fe
[ui] #150 닉네임 텍스트 필드 테두리 색상, 닉네임 중복확인 버튼 색상 변경
leeeha Aug 25, 2023
55a5015
[mod] #150 마이페이지에서 닉네임 수정할 때는 힌트로 기존 닉네임이 보이도록 구현
leeeha Aug 25, 2023
0960952
[mod] #150 닉네임 중복체크를 하지 않은 상태에서 확인 버튼을 누르면 에러 표시하도록 변경
leeeha Aug 25, 2023
9019d34
[mod] #150 스토리에서 스킵 버튼 누르면 닉네임 화면으로 이동하도록 변경
leeeha Aug 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,86 @@ import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.android.go.sopt.winey.R
import com.android.go.sopt.winey.databinding.ActivityNicknameBinding
import com.android.go.sopt.winey.domain.repository.DataStoreRepository
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.code.ErrorCode
import com.android.go.sopt.winey.util.context.hideKeyboard
import com.android.go.sopt.winey.util.context.snackBar
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.UiState
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activity_nickname) {
private val viewModel by viewModels<NicknameViewModel>()
private val prevScreenName by lazy { intent.extras?.getString(EXTRA_KEY, "") }

@Inject
lateinit var dataStoreRepository: DataStoreRepository

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.vm = viewModel
viewModel.updatePrevScreenName(prevScreenName)

switchCloseButtonVisibility()
initRootLayoutClickListener()
initCloseButtonClickListener()
switchTitleText()
switchEditTextHint()

initRootLayoutClickListener()
initEditTextWatcher()
initDuplicateCheckButtonClickListener()
initCompleteButtonClickListener()
initPatchNicknameStateObserver()
}

private fun switchCloseButtonVisibility() {
when (prevScreenName) {
STORY_SCREEN -> binding.ivNicknameClose.visibility = View.GONE
MY_PAGE_SCREEN -> binding.ivNicknameClose.visibility = View.VISIBLE
}
}

private fun initCloseButtonClickListener() {
binding.ivNicknameClose.setOnClickListener {
finish()
}
}

private fun switchTitleText() {
when (prevScreenName) {
STORY_SCREEN ->
binding.tvNicknameTitle.text =
stringOf(R.string.nickname_default_title)

MY_PAGE_SCREEN ->
binding.tvNicknameTitle.text =
stringOf(R.string.nickname_mypage_title)
}
}

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?) {}

// 텍스트가 바뀌면 중복체크 상태 false로 초기화
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val inputText = s.toString()
viewModel.updateTextChangedState(inputText.isNotBlank() && inputText != prevText)
if (inputText.isNotBlank() && inputText != prevText) {
viewModel.updateDuplicateCheckState(false)
}
prevText = inputText
}
})
}

private fun initDuplicateCheckButtonClickListener() {
binding.btnNicknameDuplicateCheck.setOnClickListener {
if (viewModel.isTextChanged.value) {
viewModel.apply {
updateDuplicateCheckButtonState(true)
getNicknameDuplicateCheck()
}
viewModel.getNicknameDuplicateCheck()
}
}

private fun initCompleteButtonClickListener() {
binding.btnNicknameComplete.setOnClickListener {
// 중복체크를 하지 않은 상태에서 완료 버튼을 클릭하면 에러 표시
if (!viewModel.isDuplicateChecked.value) {
viewModel.updateInputUiState(
InputUiState.Failure(ErrorCode.CODE_UNCHECKED_DUPLICATION)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 InputUiState로 관리하니까 훨씬 좋네요 !

)
return@setOnClickListener
}

// 서버통신 결과 중복되지 않은 닉네임인 경우에만 PATCH 서버통신 진행
if (viewModel.isValidNickname.value) {
viewModel.patchNickname()
}
}
}
Expand All @@ -111,6 +111,24 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ
binding.btnNicknameComplete.isClickable = false
}

private fun switchEditTextHint() {
lifecycleScope.launch {
when (prevScreenName) {
STORY_SCREEN -> binding.etNickname.hint = stringOf(R.string.nickname_default_hint)
MY_PAGE_SCREEN -> {
val user = dataStoreRepository.getUserInfo().first() ?: return@launch
binding.etNickname.hint = user.nickname
}
}
}
}

private fun initCloseButtonClickListener() {
binding.ivNicknameClose.setOnClickListener {
finish()
}
}

private fun initRootLayoutClickListener() {
binding.root.setOnClickListener {
hideKeyboard(binding.root)
Expand All @@ -127,7 +145,7 @@ class NicknameActivity : BindingActivity<ActivityNicknameBinding>(R.layout.activ

companion object {
private const val EXTRA_KEY = "PREV_SCREEN_NAME"
private const val MY_PAGE_SCREEN = "MyPageFragment"
private const val STORY_SCREEN = "StoryActivity"
const val MY_PAGE_SCREEN = "MyPageFragment"
const val STORY_SCREEN = "StoryActivity"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.android.go.sopt.winey.domain.repository.AuthRepository
import com.android.go.sopt.winey.util.code.ErrorCode.CODE_DUPLICATE
import com.android.go.sopt.winey.util.code.ErrorCode.CODE_INVALID_LENGTH
import com.android.go.sopt.winey.util.code.ErrorCode.CODE_SPACE_SPECIAL_CHAR
import com.android.go.sopt.winey.util.code.ErrorCode.CODE_UNCHECKED_DUPLICATION
import com.android.go.sopt.winey.util.view.InputUiState
import com.android.go.sopt.winey.util.view.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -32,12 +31,11 @@ class NicknameViewModel @Inject constructor(
val nickname: String get() = _nickname.value

private val _inputUiState: MutableStateFlow<InputUiState> =
_nickname.map { updateInputUiState(it) }
_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 @@ -46,19 +44,29 @@ class NicknameViewModel @Inject constructor(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(PRODUCE_STOP_TIMEOUT)
)
private fun validateNickname(state: InputUiState) = state == InputUiState.Success

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

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

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

private val _patchNicknameState = MutableStateFlow<UiState<Unit>>(UiState.Empty)
val patchNicknameState: StateFlow<UiState<Unit>> = _patchNicknameState.asStateFlow()
var prevScreenName: String? = null

private fun validateNickname(state: InputUiState) = state == InputUiState.Success
fun updatePrevScreenName(name: String?) {
prevScreenName = name
}

fun updateInputUiState(inputUiState: InputUiState) {
_inputUiState.value = inputUiState
}

fun updateDuplicateCheckState(checked: Boolean) {
_isDuplicateChecked.value = checked
}

fun patchNickname() {
viewModelScope.launch {
Expand Down Expand Up @@ -87,89 +95,43 @@ class NicknameViewModel @Inject constructor(
if (response == null) return@onSuccess

response.isDuplicated.let {
Timber.d("SUCCESS GET DUPLICATION CHECK: $it")
updateDuplicateCheckState(it)
saveDuplicateCheckState(it)
showDuplicateCheckResult(it)
saveDuplicateCheckResult(it)
}
updateDuplicateCheckState(true)
}
.onFailure { t ->
Timber.e("${t.message}")
}
}
}

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

// 서버통신 할 때마다 현재 닉네임과 중복여부를 저장한다.
private fun saveDuplicateCheckState(isDuplicated: Boolean) {
private fun saveDuplicateCheckResult(isDuplicated: Boolean) {
prevCheckResult = Pair(nickname, isDuplicated)
}

private fun updateInputUiState(nickname: String): InputUiState {
private fun checkInputUiState(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.Failure(CODE_SPACE_SPECIAL_CHAR)
}

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 <T> Flow<T>.mutableStateIn(
initialValue: T,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class GuideActivity : BindingActivity<ActivityGuideBinding>(R.layout.activity_gu
super.onCreate(savedInstanceState)

initViewPagerAdapter()
preventDefaultSwipe()
observePageChange()
initNextButtonClickListener()
}
Expand All @@ -25,10 +24,6 @@ class GuideActivity : BindingActivity<ActivityGuideBinding>(R.layout.activity_gu
binding.vpGuide.adapter = GuideFragmentStateAdapter(this)
}

private fun preventDefaultSwipe() {
binding.vpGuide.isUserInputEnabled = false
}

private fun observePageChange() {
binding.vpGuide.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ class FirstStoryFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

bringSpeechBubbleToFront()
bringTextToFront()
}

private fun bringSpeechBubbleToFront() {
binding.tvStoryWineyCountrySpeechBubble.bringToFront()
private fun bringTextToFront() {
with(binding) {
tvStorySaverSpeechBubble.bringToFront()
tvStoryWineyCountryName.bringToFront()
tvStoryWineyCountrySpeechBubble.bringToFront()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ class SecondStoryFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

bringSpeechBubbleToFront()
bringTextToFront()
}

private fun bringSpeechBubbleToFront() {
binding.tvStoryWineyCountrySpeechBubble.bringToFront()
private fun bringTextToFront() {
with(binding) {
tvStorySaverSpeechBubble.bringToFront()
tvStoryWineyCountryName.bringToFront()
tvStoryWineyCountrySpeechBubble.bringToFront()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class StoryActivity : BindingActivity<ActivityStoryBinding>(R.layout.activity_st
setUpDefaultNavigationText()
setUpDefaultFragment(savedInstanceState)
initNextButtonClickListener()
initSkipButtonClickListener()
}

private fun initSkipButtonClickListener() {
binding.tvStorySkip.setOnClickListener {
navigateToNicknameScreen()
}
}

private fun initNextButtonClickListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ class ThirdStoryFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

bringSpeechBubbleToFront()
bringTextToFront()
}

private fun bringSpeechBubbleToFront() {
binding.tvStoryWineyCountrySpeechBubble.bringToFront()
private fun bringTextToFront() {
with(binding) {
tvStorySaverSpeechBubble.bringToFront()
tvStoryWineyCountryName.bringToFront()
tvStoryWineyCountrySpeechBubble.bringToFront()
}
}
}
Loading