Skip to content

Commit

Permalink
[#30] 로그인 기능 추가
Browse files Browse the repository at this point in the history
- ProfileEditActivity 추가
- 이름과 닉네임 입력이 가능하고, ViewModel 의 UiModel 을 뷰에 반영시키도록 함
  • Loading branch information
ethan-223 committed Apr 26, 2022
1 parent 0f0d8e9 commit 83b9c5f
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 5 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -79,6 +80,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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,8 +74,7 @@ class LoginActivity : AppCompatActivity() {
Lg.d("Login!")
}
LoginEvent.NewUser -> {
// Todo: 회원가입
Lg.d("New user!")
ProfileEditActivity.startActivity(this)
}
LoginEvent.Error -> {
showUnknownErrorToast()
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ProfileUiModel>
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/ic_check.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="13dp"
android:viewportWidth="18"
android:viewportHeight="13">
<path
android:pathData="M17,1L6,12L1,7"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#1162FF"
android:strokeLineCap="round"/>
</vector>
20 changes: 20 additions & 0 deletions app/src/main/res/drawable/ic_toolbar_back.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:pathData="M25.2084,15.5H5.2084"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M12.5,22.7918L5.2084,15.5002L12.5,8.2085"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>
90 changes: 90 additions & 0 deletions app/src/main/res/layout/activity_profile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".profile.ProfileEditActivity">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_profile"
style="@style/Toolbar.DefaultStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_toolbar_back">

<TextView
android:id="@+id/textview_profile_title"
style="@style/Toolbar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="기본 정보" />

</androidx.appcompat.widget.Toolbar>

<com.moyerun.moyeorun_android.views.BadgeRoundImageView
android:id="@+id/badgeimageview_profile_image"
android:layout_width="73dp"
android:layout_height="73dp"
android:layout_marginTop="25dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_profile" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:orientation="vertical"
android:paddingHorizontal="21dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/badgeimageview_profile_image">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profile_name" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edittext_profile_name"
style="@style/Profile.Input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="@string/profile_name"
android:inputType="text" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/profile_nickname" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edittext_profile_nickname"
style="@style/Profile.Input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="@string/profile_nickname"
android:inputType="text" />

</LinearLayout>

<Button
android:id="@+id/button_profile_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="21dp"
android:layout_marginBottom="33dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="다음" />

</androidx.constraintlayout.widget.ConstraintLayout>
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@
<string name="login_button_start">시작하기</string>
<string name="login_button_google">구글 계정으로 로그인</string>
<string name="login_toast_unknown_error">로그인에 실패하였습니다.</string>

<!-- Profile -->
<string name="profile_name">이름</string>
<string name="profile_nickname">닉네임</string>

</resources>
13 changes: 13 additions & 0 deletions app/src/main/res/values/styles_edittext.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Profile" />

<!-- Todo: 배경 이미지 변경. BorderEditText 머지 되면 parent 로 둬야함. -->
<style name="Profile.Input">
<item name="android:textStyle">bold</item>
<item name="android:textSize">18dp</item>
<item name="android:paddingHorizontal">21dp</item>
<item name="android:paddingVertical">15dp</item>
<item name="android:background">@null</item>
</style>
</resources>
Loading

0 comments on commit 83b9c5f

Please sign in to comment.