From 5c6f4a68a1a8f422120300648329ddabe2799f8a Mon Sep 17 00:00:00 2001 From: winter223 Date: Tue, 19 Apr 2022 14:44:52 +0900 Subject: [PATCH] =?UTF-8?q?[#30]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProfileEditActivity 추가 - 이름과 닉네임 입력이 가능하고, ViewModel 의 UiModel 을 뷰에 반영시키도록 함 --- app/build.gradle | 2 + .../common/extension/ViewExtension.kt | 13 +++ .../moyeorun_android/login/LoginActivity.kt | 4 +- .../profile/ProfileEditActivity.kt | 105 ++++++++++++++++++ .../profile/ProfileEditViewModel.kt | 42 +++++++ .../profile/ProfileUiModel.kt | 11 ++ .../views/BadgeRoundImageView.kt | 6 +- app/src/main/res/drawable/ic_check.xml | 13 +++ app/src/main/res/drawable/ic_toolbar_back.xml | 20 ++++ app/src/main/res/layout/activity_profile.xml | 90 +++++++++++++++ app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/styles_edittext.xml | 11 ++ app/src/main/res/values/styles_toolbar.xml | 14 +++ build.gradle | 2 +- 14 files changed, 333 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditActivity.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditViewModel.kt create mode 100644 app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileUiModel.kt create mode 100644 app/src/main/res/drawable/ic_check.xml create mode 100644 app/src/main/res/drawable/ic_toolbar_back.xml create mode 100644 app/src/main/res/layout/activity_profile.xml create mode 100644 app/src/main/res/values/styles_toolbar.xml diff --git a/app/build.gradle b/app/build.gradle index 7deb547..d93d917 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt' + id 'kotlin-parcelize' id 'com.google.gms.google-services' id 'com.google.firebase.crashlytics' id 'dagger.hilt.android.plugin' @@ -82,6 +83,7 @@ dependencies { implementation 'androidx.activity:activity-ktx:1.4.0' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/app/src/main/java/com/moyerun/moyeorun_android/common/extension/ViewExtension.kt b/app/src/main/java/com/moyerun/moyeorun_android/common/extension/ViewExtension.kt index bb0e66e..8a31097 100644 --- a/app/src/main/java/com/moyerun/moyeorun_android/common/extension/ViewExtension.kt +++ b/app/src/main/java/com/moyerun/moyeorun_android/common/extension/ViewExtension.kt @@ -1,6 +1,9 @@ package com.moyerun.moyeorun_android.common.extension +import android.text.TextUtils import android.view.View +import android.widget.TextView +import androidx.annotation.DrawableRes fun View.setOnDebounceClickListener(interval: Long = 1000L, action: (View?) -> Unit) { val debounceClickListener = object : View.OnClickListener { @@ -16,4 +19,14 @@ fun View.setOnDebounceClickListener(interval: Long = 1000L, action: (View?) -> U } } setOnClickListener(debounceClickListener) +} + +fun TextView.setTextIfNew(text: CharSequence?) { + if (TextUtils.equals(this.text, text).not()) { + setText(text) + } +} + +fun TextView.setDrawableEnd(@DrawableRes resId: Int?) { + setCompoundDrawablesWithIntrinsicBounds(0, 0, resId ?: 0, 0) } \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/login/LoginActivity.kt b/app/src/main/java/com/moyerun/moyeorun_android/login/LoginActivity.kt index 0f21b71..ebe0ad6 100644 --- a/app/src/main/java/com/moyerun/moyeorun_android/login/LoginActivity.kt +++ b/app/src/main/java/com/moyerun/moyeorun_android/login/LoginActivity.kt @@ -20,6 +20,7 @@ import com.moyerun.moyeorun_android.common.extension.observeEvent import com.moyerun.moyeorun_android.common.extension.showNetworkErrorToast import com.moyerun.moyeorun_android.common.extension.toast import com.moyerun.moyeorun_android.databinding.ActivityLoginBinding +import com.moyerun.moyeorun_android.profile.ProfileEditActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -73,8 +74,7 @@ class LoginActivity : AppCompatActivity() { Lg.d("Login!") } LoginEvent.NewUser -> { - // Todo: 회원가입 - Lg.d("New user!") + ProfileEditActivity.startActivity(this) } LoginEvent.Error -> { showUnknownErrorToast() diff --git a/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditActivity.kt b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditActivity.kt new file mode 100644 index 0000000..d3b5f50 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditActivity.kt @@ -0,0 +1,105 @@ +package com.moyerun.moyeorun_android.profile + +import android.content.Context +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.EditText +import androidx.activity.viewModels +import androidx.core.widget.doAfterTextChanged +import com.moyerun.moyeorun_android.R +import com.moyerun.moyeorun_android.common.extension.repeatOnStart +import com.moyerun.moyeorun_android.common.extension.setDrawableEnd +import com.moyerun.moyeorun_android.common.extension.setTextIfNew +import com.moyerun.moyeorun_android.databinding.ActivityProfileBinding +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class ProfileEditActivity : AppCompatActivity() { + + private val viewModel: ProfileEditViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivityProfileBinding.inflate(layoutInflater) + setContentView(binding.root) + + val originalProfile: ProfileUiModel? = intent.getParcelableExtra(EXTRA_PROFILE_UI_MODEL) + val isNewProfile = originalProfile == null + + if (isNewProfile) { + binding.textviewProfileTitle.text = "기본 정보" + binding.buttonProfileConfirm.text = "다음" + } else { + binding.textviewProfileTitle.text = "프로필" + binding.buttonProfileConfirm.text = "완료" + } + + viewModel.updateProfile(originalProfile) + + binding.edittextProfileName.doAfterTextChanged { + viewModel.onNameChanged(it?.toString().orEmpty()) + } + + binding.edittextProfileNickname.doAfterTextChanged { + viewModel.onNicknameChanged(it?.toString().orEmpty()) + } + + repeatOnStart { + launch { + viewModel.profileUiModel + .map { it.name } + .distinctUntilChanged() + .collect { + binding.edittextProfileName.setTextAndCheckIcon(it) + } + } + launch { + viewModel.profileUiModel + .map { it.nickname } + .distinctUntilChanged() + .collect { + binding.edittextProfileNickname.setTextAndCheckIcon(it) + } + } + launch { + viewModel.profileUiModel + .map { it.imageUrl } + .distinctUntilChanged() + .collect { + binding.badgeimageviewProfileImage.setBigCircleImgSrc(it) + } + } + } + } + + private fun isValidText(text: String): Boolean { + // Todo: 유효성 검사 조건 추가 (ex. 정규 표현식, 글자 제한) + return text.isNotEmpty() + } + + private fun EditText.setTextAndCheckIcon(text: String) { + val isValid = isValidText(text) + val resId = if (isValid) { + R.drawable.ic_check + } else { + null + } + setDrawableEnd(resId) + setTextIfNew(text) + } + + companion object { + private const val EXTRA_PROFILE_UI_MODEL = "profileUiModel" + + fun startActivity(context: Context, profileUiModel: ProfileUiModel? = null) { + context.startActivity(Intent(context, ProfileEditActivity::class.java).apply { + putExtra(EXTRA_PROFILE_UI_MODEL, profileUiModel) + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditViewModel.kt b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditViewModel.kt new file mode 100644 index 0000000..2694743 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileEditViewModel.kt @@ -0,0 +1,42 @@ +package com.moyerun.moyeorun_android.profile + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update + +class ProfileEditViewModel: ViewModel() { + + private val _profileUiModel = MutableStateFlow(ProfileUiModel()) + val profileUiModel: StateFlow + get() = _profileUiModel + + private var oldProfileUiModel: ProfileUiModel? = null + + private var isNewPost = true + + fun updateProfile(profileUiModel: ProfileUiModel?) { + if (profileUiModel == null) return + _profileUiModel.update { profileUiModel } + oldProfileUiModel = profileUiModel + isNewPost = false + } + + fun onNameChanged(name: String) { + _profileUiModel.update { + it.copy(name = name) + } + } + + fun onNicknameChanged(nickname: String) { + _profileUiModel.update { + it.copy(nickname = nickname) + } + } + + fun onImageUrlChanged(imageUrl: String) { + _profileUiModel.update { + it.copy(imageUrl = imageUrl) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileUiModel.kt b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileUiModel.kt new file mode 100644 index 0000000..5a35e90 --- /dev/null +++ b/app/src/main/java/com/moyerun/moyeorun_android/profile/ProfileUiModel.kt @@ -0,0 +1,11 @@ +package com.moyerun.moyeorun_android.profile + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class ProfileUiModel( + val imageUrl: String = "", + val name: String = "", + val nickname: String = "" +): Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/moyerun/moyeorun_android/views/BadgeRoundImageView.kt b/app/src/main/java/com/moyerun/moyeorun_android/views/BadgeRoundImageView.kt index 9108791..525530d 100644 --- a/app/src/main/java/com/moyerun/moyeorun_android/views/BadgeRoundImageView.kt +++ b/app/src/main/java/com/moyerun/moyeorun_android/views/BadgeRoundImageView.kt @@ -88,9 +88,11 @@ class BadgeRoundImageView @JvmOverloads constructor( @RequiresPermission(Manifest.permission.INTERNET) fun setBigCircleImgSrc(imgUrl: String, isImageCropped: Boolean = false) { if (isImageCropped) { - Glide.with(context).load(imgUrl).centerCrop().into(binding.imgBigCircle) + Glide.with(context).load(imgUrl).centerCrop() + .placeholder(R.drawable.user_profile_image_default_112dp).into(binding.imgBigCircle) } else - Glide.with(context).load(imgUrl).into(binding.imgBigCircle) + Glide.with(context).load(imgUrl) + .placeholder(R.drawable.user_profile_image_default_112dp).into(binding.imgBigCircle) } fun setBigCircleImageBg(@ColorRes bgResId: Int) { diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..49fd67d --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_toolbar_back.xml b/app/src/main/res/drawable/ic_toolbar_back.xml new file mode 100644 index 0000000..128ab24 --- /dev/null +++ b/app/src/main/res/drawable/ic_toolbar_back.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml new file mode 100644 index 0000000..8cb96a0 --- /dev/null +++ b/app/src/main/res/layout/activity_profile.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + +