From dfe3b291a1bdbd55bdb3c4d1d56b1a2c97dc12c0 Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 21 Mar 2025 04:40:39 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 +- .../kr/co/vividnext/sodalive/user/Gender.kt | 2 + .../kr/co/vividnext/sodalive/user/UserApi.kt | 15 +- .../vividnext/sodalive/user/UserRepository.kt | 7 +- .../sodalive/user/signup/SignUpActivity.kt | 156 +----- .../sodalive/user/signup/SignUpRequest.kt | 3 - .../sodalive/user/signup/SignUpViewModel.kt | 107 +--- app/src/main/res/layout/activity_login.xml | 4 +- app/src/main/res/layout/activity_signup.xml | 522 +++++------------- 9 files changed, 178 insertions(+), 642 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4717a5c..4d64259 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,7 +98,9 @@ - + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/Gender.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/Gender.kt index 7a0c27f..30c099d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/Gender.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/Gender.kt @@ -5,8 +5,10 @@ import com.google.gson.annotations.SerializedName enum class Gender { @SerializedName("MALE") MALE, + @SerializedName("FEMALE") FEMALE, + @SerializedName("NONE") NONE } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt index 0153a1a..61520a3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt @@ -4,7 +4,6 @@ import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.explorer.profile.MemberBlockRequest import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser -import kr.co.vividnext.sodalive.main.GaidUpdateRequest import kr.co.vividnext.sodalive.main.MarketingInfoUpdateRequest import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest import kr.co.vividnext.sodalive.mypage.MyPageResponse @@ -18,8 +17,8 @@ import kr.co.vividnext.sodalive.settings.signout.SignOutRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.login.LoginRequest import kr.co.vividnext.sodalive.user.login.LoginResponse +import kr.co.vividnext.sodalive.user.signup.SignUpRequest import okhttp3.MultipartBody -import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -34,11 +33,9 @@ interface UserApi { @POST("/member/login") fun login(@Body request: LoginRequest): Single> - @POST("/member/signup") - @Multipart + @POST("/member/signup/v2") fun signUp( - @Part profileImage: MultipartBody.Part?, - @Part("request") request: RequestBody + @Body request: SignUpRequest ): Single> @POST("/member/forgot-password") @@ -142,12 +139,6 @@ interface UserApi { @Header("Authorization") authHeader: String ): Single> - @PUT("/member/adid/update") - fun updateGaid( - @Body request: GaidUpdateRequest, - @Header("Authorization") authHeader: String - ): Single> - @PUT("/member/marketing-info/update") fun updateMarketingInfo( @Body request: MarketingInfoUpdateRequest, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt index f9ca8a8..1ae028c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt @@ -13,16 +13,13 @@ import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingR import kr.co.vividnext.sodalive.settings.signout.SignOutRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.login.LoginRequest +import kr.co.vividnext.sodalive.user.signup.SignUpRequest import okhttp3.MultipartBody -import okhttp3.RequestBody class UserRepository(private val userApi: UserApi) { fun login(request: LoginRequest) = userApi.login(request) - fun signUp(profileImage: MultipartBody.Part?, request: RequestBody) = userApi.signUp( - profileImage, - request - ) + fun signUp(request: SignUpRequest) = userApi.signUp(request) fun findPassword(request: ForgotPasswordRequest) = userApi.findPassword(request = request) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpActivity.kt index b1e9099..dbcdfa0 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpActivity.kt @@ -1,99 +1,50 @@ package kr.co.vividnext.sodalive.user.signup +import android.app.Service import android.content.Intent import android.os.Bundle -import android.view.View +import android.os.Handler +import android.os.Looper +import android.view.inputmethod.InputMethodManager import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.activity.result.contract.ActivityResultContracts -import coil.load -import coil.transform.RoundedCornersTransformation -import com.github.dhaval2404.imagepicker.ImagePicker +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi import com.jakewharton.rxbinding4.widget.textChanges import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog -import kr.co.vividnext.sodalive.common.RealPathUtil import kr.co.vividnext.sodalive.databinding.ActivitySignupBinding -import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.main.MainActivity import kr.co.vividnext.sodalive.settings.terms.TermsActivity -import kr.co.vividnext.sodalive.user.Gender import org.koin.android.ext.android.inject +@OptIn(UnstableApi::class) class SignUpActivity : BaseActivity(ActivitySignupBinding::inflate) { private val viewModel: SignUpViewModel by inject() - private val onBackPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - onClickBackButton() - } - } private lateinit var loadingDialog: LoadingDialog + private lateinit var imm: InputMethodManager - private val imageResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - val resultCode = result.resultCode - val data = result.data - - if (resultCode == RESULT_OK) { - // Image Uri will not be null for RESULT_OK - val fileUri = data?.data!! - binding.ivProfile.background = null - binding.ivProfile.load(fileUri) { - crossfade(true) - transformations(RoundedCornersTransformation(13.3f.dpToPx())) - } - viewModel.profileImageUri = fileUri - } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() - } - } + private val handler = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { + imm = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager + super.onCreate(savedInstanceState) - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - - viewModel.getRealPathFromURI = { - RealPathUtil.getRealPath(applicationContext, it) - } - bindData() } override fun setupView() { binding.toolbar.tvBack.text = "회원가입" - binding.toolbar.tvBack.setOnClickListener { onClickBackButton() } - + binding.toolbar.tvBack.setOnClickListener { finish() } loadingDialog = LoadingDialog(this, layoutInflater) - binding.ivPhotoPicker.setOnClickListener { - ImagePicker.with(this) - .crop() - .galleryOnly() - .galleryMimeTypes( // Exclude gif images - mimeTypes = arrayOf( - "image/png", - "image/jpg", - "image/jpeg" - ) - ) - .createIntent { imageResult.launch(it) } - } - - binding.tvMale.setOnClickListener { - viewModel.changeGender(Gender.MALE) - } - - binding.tvFemale.setOnClickListener { - viewModel.changeGender(Gender.FEMALE) - } - - binding.tvNone.setOnClickListener { - viewModel.changeGender(Gender.NONE) + binding.etEmail.post { + binding.etEmail.requestFocus() + imm.showSoftInput(binding.etEmail, InputMethodManager.SHOW_IMPLICIT) } binding.tvTermsOfService.setOnClickListener { @@ -108,15 +59,14 @@ class SignUpActivity : BaseActivity(ActivitySignupBinding startActivity(intent) } - binding.ivTermsOfService.setOnClickListener { - viewModel.onClickCheckboxTermsOfService() - } + binding.rlTermsOfService.setOnClickListener { viewModel.onClickCheckboxTermsOfService() } + binding.ivTermsOfService.setOnClickListener { viewModel.onClickCheckboxTermsOfService() } - binding.ivPrivacyPolicy.setOnClickListener { - viewModel.onClickCheckboxPrivacyPolicy() - } + binding.rlPrivacyPolicy.setOnClickListener { viewModel.onClickCheckboxPrivacyPolicy() } + binding.ivPrivacyPolicy.setOnClickListener { viewModel.onClickCheckboxPrivacyPolicy() } binding.tvSignUp.setOnClickListener { + hideKeyboard() viewModel.signUp { it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } finishAffinity() @@ -154,24 +104,6 @@ class SignUpActivity : BaseActivity(ActivitySignupBinding } ) - compositeDisposable.add( - binding.etPasswordRe.textChanges().skip(1) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - viewModel.passwordRe = it.toString() - } - ) - - compositeDisposable.add( - binding.etNickname.textChanges().skip(1) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - viewModel.nickname = it.toString() - } - ) - viewModel.isAgreeTermsOfServiceLiveData.observe(this) { binding.ivTermsOfService.isSelected = it } @@ -180,40 +112,19 @@ class SignUpActivity : BaseActivity(ActivitySignupBinding binding.ivPrivacyPolicy.isSelected = it } - viewModel.genderLiveData.observe(this) { - binding.tvMale.isSelected = false - binding.tvFemale.isSelected = false - binding.tvNone.isSelected = false - - when (it) { - Gender.MALE -> binding.tvMale.isSelected = true - Gender.FEMALE -> binding.tvFemale.isSelected = true - Gender.NONE -> binding.tvNone.isSelected = true - else -> { - } - } - } - viewModel.signUpErrorLiveData.observe(this) { Toast.makeText(applicationContext, it.message, Toast.LENGTH_LONG).show() when (it.errorProperty) { "email" -> { - viewModel.setStep(step = SignUpViewModel.EmailSignUpStep.STEP_1) binding.etEmail.error = it.message binding.etEmail.requestFocus() } "password" -> { - viewModel.setStep(step = SignUpViewModel.EmailSignUpStep.STEP_1) binding.etPassword.error = it.message binding.etPassword.requestFocus() } - - "nickname" -> { - binding.etNickname.error = it.message - binding.etNickname.requestFocus() - } } } @@ -228,27 +139,14 @@ class SignUpActivity : BaseActivity(ActivitySignupBinding viewModel.toastLiveData.observe(this) { it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } } - - viewModel.stepLiveData.observe(this) { - if (it == SignUpViewModel.EmailSignUpStep.STEP_2) { - binding.toolbar.tvBack.text = "프로필 설정" - binding.tvSignUp.text = "회원가입" - binding.llStep1.visibility = View.GONE - binding.llStep2.visibility = View.VISIBLE - } else { - binding.toolbar.tvBack.text = "회원가입" - binding.tvSignUp.text = "다음" - binding.llStep1.visibility = View.VISIBLE - binding.llStep2.visibility = View.GONE - } - } } - private fun onClickBackButton() { - if (viewModel.stepLiveData.value!! == SignUpViewModel.EmailSignUpStep.STEP_2) { - viewModel.setStep(SignUpViewModel.EmailSignUpStep.STEP_1) - } else { - finish() - } + private fun hideKeyboard() { + handler.postDelayed({ + imm.hideSoftInputFromWindow( + window.decorView.applicationWindowToken, + InputMethodManager.HIDE_NOT_ALWAYS + ) + }, 100) } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpRequest.kt index 3df7a24..188f7af 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpRequest.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpRequest.kt @@ -2,14 +2,11 @@ package kr.co.vividnext.sodalive.user.signup import androidx.annotation.Keep import com.google.gson.annotations.SerializedName -import kr.co.vividnext.sodalive.user.Gender @Keep data class SignUpRequest( @SerializedName("email") val email: String, @SerializedName("password") val password: String, - @SerializedName("nickname") val nickname: String, - @SerializedName("gender") val gender: Gender, @SerializedName("marketingPid") val marketingPid: String, @SerializedName("isAgreeTermsOfService") val isAgreeTermsOfService: Boolean, @SerializedName("isAgreePrivacyPolicy") val isAgreePrivacyPolicy: Boolean, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpViewModel.kt index eb68329..5c0b06d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/signup/SignUpViewModel.kt @@ -1,42 +1,19 @@ package kr.co.vividnext.sodalive.user.signup -import android.net.Uri import androidx.core.util.PatternsCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.google.gson.Gson -import com.google.gson.annotations.SerializedName import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.tracking.FirebaseTracking -import kr.co.vividnext.sodalive.user.Gender import kr.co.vividnext.sodalive.user.UserRepository -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.MultipartBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import java.io.File class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() { - enum class EmailSignUpStep { - @SerializedName("STEP_1") - STEP_1, - @SerializedName("STEP_2") - STEP_2 - } - var email = "" var password = "" - var passwordRe = "" - var nickname = "" - var profileImageUri: Uri? = null - - private val _genderLiveData = MutableLiveData(Gender.NONE) - val genderLiveData: LiveData - get() = _genderLiveData private val _isAgreeTermsOfServiceLiveData = MutableLiveData(false) val isAgreeTermsOfServiceLiveData: LiveData @@ -58,55 +35,19 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() val isLoading: LiveData get() = _isLoading - private val _stepLiveData = MutableLiveData(EmailSignUpStep.STEP_1) - val stepLiveData: LiveData - get() = _stepLiveData - - lateinit var getRealPathFromURI: (Uri) -> String? - - fun setStep(step: EmailSignUpStep = EmailSignUpStep.STEP_2) { - _stepLiveData.postValue(step) - } - fun signUp(onSuccess: (String?) -> Unit) { - if (stepLiveData.value!! == EmailSignUpStep.STEP_1) { - if (validationStep1()) return - - setStep() - return - } - - if (validationStep2()) return - + if (!validation()) return val request = SignUpRequest( email = email, password = password, - nickname = nickname, - gender = _genderLiveData.value!!, marketingPid = SharedPreferenceManager.marketingPid, isAgreeTermsOfService = _isAgreeTermsOfServiceLiveData.value!!, isAgreePrivacyPolicy = _isAgreePrivacyPolicyLiveData.value!! ) - val requestJson = Gson().toJson(request) - - val profileImage = if (profileImageUri != null) { - val file = File(getRealPathFromURI(profileImageUri!!)) - MultipartBody.Part.createFormData( - "profileImage", - file.name, - file.asRequestBody("image/*".toMediaType()) - ) - } else { - null - } - _isLoading.value = true compositeDisposable.add( - repository.signUp( - profileImage, - requestJson.toRequestBody("text/plain".toMediaType()) - ) + repository.signUp(request) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -140,7 +81,7 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() ) } - private fun validationStep1(): Boolean { + private fun validation(): Boolean { if (email.isBlank()) { _signUpErrorLiveData.postValue( SignUpError( @@ -149,7 +90,7 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() ) ) - return true + return false } if (password.isBlank()) { @@ -160,18 +101,7 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() ) ) - return true - } - - if (password != passwordRe) { - _signUpErrorLiveData.postValue( - SignUpError( - "password", - "비밀번호가 일치하지 않습니다." - ) - ) - - return true + return false } if ( @@ -185,7 +115,7 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() ) ) - return true + return false } if (!PatternsCompat.EMAIL_ADDRESS.matcher(email).matches()) { @@ -196,7 +126,7 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() ) ) - return true + return false } if ( @@ -211,24 +141,11 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() "영문, 숫자 포함 8자 이상의 비밀번호를 입력해 주세요." ) ) + + return false } - return false - } - - private fun validationStep2(): Boolean { - if (nickname.isBlank() || nickname.length < 2) { - _signUpErrorLiveData.postValue( - SignUpError( - "nickname", - "닉네임은 2자 이상 입력해 주세요." - ) - ) - - return true - } - - return false + return true } fun onClickCheckboxTermsOfService() { @@ -238,8 +155,4 @@ class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() fun onClickCheckboxPrivacyPolicy() { _isAgreePrivacyPolicyLiveData.postValue(!_isAgreePrivacyPolicyLiveData.value!!) } - - fun changeGender(gender: Gender) { - _genderLiveData.postValue(gender) - } } diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index fad02f0..d998fe4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -104,8 +104,9 @@ android:id="@+id/tv_forgot_password" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="40dp" + android:layout_marginTop="30dp" android:fontFamily="@font/gmarket_sans_medium" + android:paddingVertical="10dp" android:text="비밀번호를 잊으셨나요?" android:textColor="@color/color_bbbbbb" android:textSize="13.3sp" /> @@ -116,6 +117,7 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:fontFamily="@font/gmarket_sans_medium" + android:paddingVertical="10dp" android:text="보이스온 회원이 아닌가요? 지금 가입하세요." android:textColor="@color/color_bbbbbb" android:textSize="13.3sp" /> diff --git a/app/src/main/res/layout/activity_signup.xml b/app/src/main/res/layout/activity_signup.xml index 1e5078d..e83d642 100644 --- a/app/src/main/res/layout/activity_signup.xml +++ b/app/src/main/res/layout/activity_signup.xml @@ -1,7 +1,6 @@ - + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="20dp" + android:hint="이메일" + app:boxBackgroundColor="@color/color_cc333333" + app:boxBackgroundMode="filled" + app:boxCornerRadiusBottomEnd="6.7dp" + app:boxCornerRadiusBottomStart="6.7dp" + app:boxCornerRadiusTopEnd="6.7dp" + app:boxCornerRadiusTopStart="6.7dp" + app:boxStrokeColor="@color/color_cc333333"> - + android:fontFamily="@font/gmarket_sans_medium" + android:importantForAutofill="no" + android:inputType="textEmailAddress" + android:paddingVertical="16dp" + android:textColor="@color/color_eeeeee" + android:textSize="15sp" /> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="16dp" + android:hint="비밀번호" + app:boxBackgroundColor="@color/color_cc333333" + app:boxBackgroundMode="filled" + app:boxCornerRadiusBottomEnd="6.7dp" + app:boxCornerRadiusBottomStart="6.7dp" + app:boxCornerRadiusTopEnd="6.7dp" + app:boxCornerRadiusTopStart="6.7dp" + app:boxStrokeColor="@color/color_cc333333" + app:endIconMode="password_toggle"> - + + - + - + + + + - + android:fontFamily="@font/gmarket_sans_medium" + android:text="이용약관" + android:textColor="@color/color_eeeeee" + android:textSize="12sp" /> + + + + + + + + + android:layout_centerVertical="true" + android:layout_marginStart="10dp" + android:layout_toEndOf="@+id/iv_privacy_policy"> - + android:fontFamily="@font/gmarket_sans_medium" + android:text="개인정보수집 및 이용동의" + android:textColor="@color/color_eeeeee" + android:textSize="12sp" /> - - - - - - - - - - - - - - - - - - - + android:layout_marginStart="6.7dp" + android:fontFamily="@font/gmarket_sans_medium" + android:text="(필수)" + android:textColor="@color/color_3bb9f1" + android:textSize="12sp" /> - + - - - - - + android:background="@drawable/bg_round_corner_10_3bb9f1" + android:fontFamily="@font/gmarket_sans_bold" + android:gravity="center" + android:paddingVertical="16dp" + android:text="회원가입" + android:textColor="@color/white" + android:textSize="18.3sp" />