diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b620655..44a0a501 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,10 @@ + + diff --git a/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt b/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt index a52f974d..dfb3c0e4 100644 --- a/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt +++ b/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt @@ -32,4 +32,5 @@ fun Context.drawableOf(@DrawableRes resId: Int) = ContextCompat.getDrawable(this fun Context.hideKeyboard(view: View) { val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) -} \ No newline at end of file + view.clearFocus() +} diff --git a/domain/src/main/kotlin/com/going/domain/entity/NameState.kt b/domain/src/main/kotlin/com/going/domain/entity/NameState.kt new file mode 100644 index 00000000..413b375a --- /dev/null +++ b/domain/src/main/kotlin/com/going/domain/entity/NameState.kt @@ -0,0 +1,5 @@ +package com.going.domain.entity + +enum class NameState { + Empty, Success, Blank +} diff --git a/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingActivity.kt b/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingActivity.kt new file mode 100644 index 00000000..de381243 --- /dev/null +++ b/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingActivity.kt @@ -0,0 +1,109 @@ +package com.going.presentation.onboarding + +import android.os.Bundle +import android.view.inputmethod.EditorInfo +import androidx.activity.viewModels +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.going.domain.entity.NameState +import com.going.presentation.R +import com.going.presentation.databinding.ActivityOnboardingProfileSettingBinding +import com.going.ui.base.BaseActivity +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class OnboardingProfileSettingActivity : + BaseActivity(R.layout.activity_onboarding_profile_setting) { + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initBindingViewModel() + initOnLineInfoEditorActionListener() + initSetOnFucusChangeListener() + observeIsProfileAvailable() + observeTextLength() + observeIsNameAvailable() + } + + private fun initBindingViewModel() { + binding.viewModel = viewModel + } + + private fun initOnLineInfoEditorActionListener() { + binding.etOnboardingProfileSettingInfo.setOnEditorActionListener { view, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) view.clearFocus() + false + } + } + + private fun initSetOnFucusChangeListener() { + binding.etOnboardingProfileSettingName.setOnFocusChangeListener { _, hasFocus -> + judgeCounterColorWithFocus(hasFocus) + } + + binding.etOnboardingProfileSettingInfo.setOnFocusChangeListener { _, hasFocus -> + judgeCounterColorWithFocus(hasFocus) + } + } + + private fun judgeCounterColorWithFocus(hasFocus: Boolean) { + if (hasFocus) { + setNameCounterColor(R.color.gray_700) + } else { + setNameCounterColor(R.color.gray_200) + } + if (viewModel.isNameAvailable.value == NameState.Blank) { + setNameCounterColor(R.color.red_400) + } + } + + private fun setNameCounterColor(color: Int) { + binding.tvNameCounter.setTextColor(getColor(color)) + } + + private fun observeIsProfileAvailable() { + viewModel.isMoveScreenAvailable.flowWithLifecycle(lifecycle).onEach { isEnd -> + if (isEnd) moveSplash() + }.launchIn(lifecycleScope) + } + + // 커스텀 글자수 제한 함수 + private fun observeTextLength() { + viewModel.nowNameLength.observe(this) { length -> + val maxNameLength = viewModel.getMaxNameLen() + + if (length > maxNameLength) { + binding.etOnboardingProfileSettingName.apply { + setText(text?.subSequence(0, maxNameLength)) + setSelection(maxNameLength) + } + } + } + + viewModel.nowInfoLength.observe(this) { length -> + val maxInfoLength = viewModel.getMaxInfoLen() + + if (length > maxInfoLength) { + binding.etOnboardingProfileSettingInfo.apply { + setText(text?.subSequence(0, maxInfoLength)) + setSelection(maxInfoLength) + } + } + } + } + + private fun observeIsNameAvailable() { + viewModel.isNameAvailable.observe(this) { state -> + when (state) { + NameState.Blank -> binding.tvNameCounter.setTextColor(getColor(R.color.red_400)) + else -> binding.tvNameCounter.setTextColor(getColor(R.color.gray_700)) + } + } + } + + private fun moveSplash() { + // 스플래시로 이동 + } +} diff --git a/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingViewModel.kt b/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingViewModel.kt new file mode 100644 index 00000000..73c90f54 --- /dev/null +++ b/presentation/src/main/java/com/going/presentation/onboarding/OnboardingProfileSettingViewModel.kt @@ -0,0 +1,63 @@ +package com.going.presentation.onboarding + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.going.domain.entity.NameState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import java.text.BreakIterator + +class OnboardingProfileSettingViewModel : ViewModel() { + val name = MutableLiveData(String()) + val nowNameLength = MutableLiveData(0) + val info = MutableLiveData(String()) + val nowInfoLength = MutableLiveData(0) + + val isNameAvailable = MutableLiveData(NameState.Empty) + val isProfileAvailable = MutableLiveData(false) + + private val _isMoveScreenAvailable = MutableStateFlow(false) + val isMoveScreenAvailable: StateFlow = _isMoveScreenAvailable + + fun getMaxNameLen() = MAX_NAME_LEN + fun getMaxInfoLen() = MAX_INFO_LEN + + fun checkProfileAvailable() { + nowNameLength.value = getGraphemeLength(name.value) + nowInfoLength.value = getGraphemeLength(info.value) + + isNameAvailable.value = when { + nowNameLength.value == 0 -> NameState.Empty + name.value.isNullOrBlank() -> NameState.Blank + else -> NameState.Success + } + + val isInfoAvailable = getGraphemeLength(info.value) in 1..MAX_INFO_LEN + + isProfileAvailable.value = + (isNameAvailable.value == NameState.Success) && isInfoAvailable + } + + // 이모지 포함 글자 수 세는 함수 + private fun getGraphemeLength(value: String?): Int { + BREAK_ITERATOR.setText(value) + + var count = 0 + while (BREAK_ITERATOR.next() != BreakIterator.DONE) { + count++ + } + + return count + } + + fun setIsMoveScreenAvailable() { + _isMoveScreenAvailable.value = true + } + + companion object { + val BREAK_ITERATOR: BreakIterator = BreakIterator.getCharacterInstance() + + const val MAX_NAME_LEN = 3 + const val MAX_INFO_LEN = 20 + } +} diff --git a/presentation/src/main/res/drawable/sel_rounded_corner_button.xml b/presentation/src/main/res/drawable/sel_rounded_corner_button.xml new file mode 100644 index 00000000..59dbca25 --- /dev/null +++ b/presentation/src/main/res/drawable/sel_rounded_corner_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/drawable/sel_rounded_corner_edit_text.xml b/presentation/src/main/res/drawable/sel_rounded_corner_edit_text.xml new file mode 100644 index 00000000..ab546cbf --- /dev/null +++ b/presentation/src/main/res/drawable/sel_rounded_corner_edit_text.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/drawable/sel_rounded_corner_edit_text_error.xml b/presentation/src/main/res/drawable/sel_rounded_corner_edit_text_error.xml new file mode 100644 index 00000000..ffe66611 --- /dev/null +++ b/presentation/src/main/res/drawable/sel_rounded_corner_edit_text_error.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/presentation/src/main/res/layout/activity_login.xml b/presentation/src/main/res/layout/activity_login.xml index 090f1f13..44eb7e06 100644 --- a/presentation/src/main/res/layout/activity_login.xml +++ b/presentation/src/main/res/layout/activity_login.xml @@ -19,7 +19,7 @@ android:gravity="center" android:lineHeight="42dp" android:text="@string/sign_in_tv_title" - android:textColor="@color/white" + android:textColor="@color/white_000" app:layout_constraintBottom_toTopOf="@id/iv_sign_in" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -58,7 +58,7 @@ android:gravity="center" android:lineHeight="42dp" android:text="@string/sign_in_tv_terms" - android:textColor="@color/white" + android:textColor="@color/white_000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/btn_sign_in" /> diff --git a/presentation/src/main/res/layout/activity_onboarding_profile_setting.xml b/presentation/src/main/res/layout/activity_onboarding_profile_setting.xml new file mode 100644 index 00000000..ac67678e --- /dev/null +++ b/presentation/src/main/res/layout/activity_onboarding_profile_setting.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 4aa2c76e..03cffe25 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -7,8 +7,19 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 서버 통신에 실패했습니다. + 이름에는 공백만 입력할 수 없어요 여행을 시작해보세요 개인정보처리방침 + %1$d / %2$d + + + 프로필 생성 + 이름 + 이름을 입력해주세요 + 한줄 소개 + 당신을 한줄로 표현해보세요 + 유형 검사하러 가기 + diff --git a/presentation/src/main/res/values/themes.xml b/presentation/src/main/res/values/themes.xml index 9cf085bd..5afe8ffa 100644 --- a/presentation/src/main/res/values/themes.xml +++ b/presentation/src/main/res/values/themes.xml @@ -10,4 +10,25 @@ + + + + + +