From be7c7d06829b9af1647e45945dd3694f74b8d3d5 Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 18 Aug 2023 19:39:04 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 10 + .../sodalive/mypage/MyPageFragment.kt | 12 +- .../mypage/profile/ProfileResponse.kt | 20 + .../mypage/profile/ProfileUpdateActivity.kt | 277 +++++++++ .../mypage/profile/ProfileUpdateRequest.kt | 20 + .../mypage/profile/ProfileUpdateViewModel.kt | 290 ++++++++++ .../GetChangeNicknamePriceResponse.kt | 5 + .../nickname/NicknameUpdateActivity.kt | 83 +++ .../nickname/NicknameUpdateViewModel.kt | 142 +++++ .../password/ModifyPasswordActivity.kt | 72 +++ .../mypage/profile/tag/MemberTagAdapter.kt | 76 +++ .../mypage/profile/tag/MemberTagApi.kt | 13 + .../mypage/profile/tag/MemberTagFragment.kt | 117 ++++ .../mypage/profile/tag/MemberTagRepository.kt | 5 + .../mypage/profile/tag/MemberTagResponse.kt | 9 + .../mypage/profile/tag/MemberTagViewModel.kt | 53 ++ .../kr/co/vividnext/sodalive/user/UserApi.kt | 36 ++ .../vividnext/sodalive/user/UserRepository.kt | 29 + .../drawable/bg_round_corner_30_9970ff.xml | 7 + .../res/drawable/bg_round_corner_8_9970ff.xml | 8 + .../res/layout/activity_modify_password.xml | 177 ++++++ .../res/layout/activity_nickname_update.xml | 111 ++++ .../res/layout/activity_profile_update.xml | 532 ++++++++++++++++++ .../main/res/layout/fragment_creator_tag.xml | 51 ++ 25 files changed, 2156 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileResponse.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateRequest.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/GetChangeNicknamePriceResponse.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/password/ModifyPasswordActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagApi.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagFragment.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagRepository.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagResponse.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagViewModel.kt create mode 100644 app/src/main/res/drawable/bg_round_corner_30_9970ff.xml create mode 100644 app/src/main/res/drawable/bg_round_corner_8_9970ff.xml create mode 100644 app/src/main/res/layout/activity_modify_password.xml create mode 100644 app/src/main/res/layout/activity_nickname_update.xml create mode 100644 app/src/main/res/layout/activity_profile_update.xml create mode 100644 app/src/main/res/layout/fragment_creator_tag.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bb051ce..6114eca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,9 @@ + + + (FragmentMyBinding::inflat ) } - binding.ivEdit.setOnClickListener {} + binding.ivEdit.setOnClickListener { + startActivity( + Intent( + requireActivity(), + ProfileUpdateActivity::class.java + ) + ) + } binding.llTotalCan.setOnClickListener { startActivity( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileResponse.kt new file mode 100644 index 0000000..9d61451 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileResponse.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.mypage.profile + +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.user.Gender + +data class ProfileResponse( + @SerializedName("userId") val userId: Long, + @SerializedName("email") val email: String, + @SerializedName("nickname") val nickname: String, + @SerializedName("gender") val gender: Gender, + @SerializedName("profileUrl") val profileUrl: String, + @SerializedName("chargeCoin") val chargeCoin: Int, + @SerializedName("rewardCoin") val rewardCoin: Int, + @SerializedName("youtubeUrl") val youtubeUrl: String?, + @SerializedName("instagramUrl") val instagramUrl: String?, + @SerializedName("blogUrl") val blogUrl: String?, + @SerializedName("websiteUrl") val websiteUrl: String?, + @SerializedName("introduce") val introduce: String, + @SerializedName("tags") val tags: List +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt new file mode 100644 index 0000000..03ec738 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateActivity.kt @@ -0,0 +1,277 @@ +package kr.co.vividnext.sodalive.mypage.profile + +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import coil.load +import coil.transform.CircleCropTransformation +import com.github.dhaval2404.imagepicker.ImagePicker +import com.jakewharton.rxbinding4.widget.textChanges +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.RealPathUtil +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ActivityProfileUpdateBinding +import kr.co.vividnext.sodalive.databinding.ItemLiveTagSelectedBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.mypage.profile.nickname.NicknameUpdateActivity +import kr.co.vividnext.sodalive.mypage.profile.password.ModifyPasswordActivity +import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagFragment +import kr.co.vividnext.sodalive.user.Gender +import org.koin.android.ext.android.inject +import java.util.concurrent.TimeUnit + +class ProfileUpdateActivity : BaseActivity( + ActivityProfileUpdateBinding::inflate +) { + + private val viewModel: ProfileUpdateViewModel by inject() + + 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 + viewModel.updateProfileImage(fileUri) { + binding.ivProfile.load(it) { + crossfade(true) + transformations(CircleCropTransformation()) + } + } + } else if (resultCode == ImagePicker.RESULT_ERROR) { + Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() + } + } + + private val tagFragment: MemberTagFragment by lazy { + MemberTagFragment(viewModel.tags) { tag, isChecked -> + when { + isChecked -> { + viewModel.addTag(tag) + return@MemberTagFragment true + } + + !isChecked -> { + viewModel.removeTag(tag) + return@MemberTagFragment true + } + + else -> { + return@MemberTagFragment false + } + } + } + } + + private lateinit var loadingDialog: LoadingDialog + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.getRealPathFromURI = { + RealPathUtil.getRealPath(applicationContext, it) + } + + bindData() + } + + override fun onStart() { + super.onStart() + + viewModel.getUserInfo() + } + + private fun bindData() { + compositeDisposable.add( + binding.etBlog.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.blogUrl = it.toString() + } + ) + + compositeDisposable.add( + binding.etWebsite.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.websiteUrl = it.toString() + } + ) + + compositeDisposable.add( + binding.etYoutube.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.youtubeUrl = it.toString() + } + ) + + compositeDisposable.add( + binding.etInstagram.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.instagramUrl = it.toString() + } + ) + + 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.userInfoLiveData.observe(this) { + binding.ivProfile.load(it.profileUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + + binding.tvEmail.text = it.email + binding.tvNickname.text = it.nickname + it.youtubeUrl?.let { url -> binding.etYoutube.setText(url) } + it.instagramUrl?.let { url -> binding.etInstagram.setText(url) } + it.websiteUrl?.let { url -> binding.etWebsite.setText(url) } + it.blogUrl?.let { url -> binding.etBlog.setText(url) } + binding.etIntroduce.setText(it.introduce) + + SharedPreferenceManager.nickname = it.nickname + } + + viewModel.toastLiveData.observe(this) { + it?.let { + Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() + } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth) + } else { + loadingDialog.dismiss() + } + } + + viewModel.selectedTagLiveData.observe(this) { + binding.llSelectTags.removeAllViews() + binding.llSelectTags.visibility = if (it.isNotEmpty()) { + View.VISIBLE + } else { + View.GONE + } + for (index in it.indices) { + val tag = it[index] + val itemView = ItemLiveTagSelectedBinding.inflate(layoutInflater) + itemView.tvTag.text = tag + itemView.ivRemove.setOnClickListener { + viewModel.removeTag(tag) + } + binding.llSelectTags.addView(itemView.root) + + if (index > 0) { + val layoutParams = itemView.root.layoutParams as LinearLayout.LayoutParams + layoutParams.marginStart = 10.dpToPx().toInt() + itemView.root.layoutParams = layoutParams + } + } + } + } + + override fun setupView() { + binding.toolbar.tvBack.text = "프로필 수정" + binding.toolbar.tvBack.setOnClickListener { finish() } + + loadingDialog = LoadingDialog(this, layoutInflater) + + compositeDisposable.add( + binding.etIntroduce.textChanges() + .debounce(500, TimeUnit.MILLISECONDS) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.introduce = it.toString() + } + ) + + binding.tvMale.setOnClickListener { + viewModel.changeGender(Gender.MALE) + } + + binding.tvFemale.setOnClickListener { + viewModel.changeGender(Gender.FEMALE) + } + + binding.tvNone.setOnClickListener { + viewModel.changeGender(Gender.NONE) + } + + 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.tvSelectTag.setOnClickListener { + if (tagFragment.isAdded) return@setOnClickListener + + tagFragment.show(supportFragmentManager, tagFragment.tag) + } + + binding.tvModifyPassword.setOnClickListener { + startActivity( + Intent( + applicationContext, + ModifyPasswordActivity::class.java + ) + ) + } + + binding.tvSave.setOnClickListener { + viewModel.updateProfile { + finish() + } + } + + binding.tvChangeNickname.setOnClickListener { + startActivity( + Intent( + applicationContext, + NicknameUpdateActivity::class.java + ) + ) + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateRequest.kt new file mode 100644 index 0000000..676314b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateRequest.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.mypage.profile + +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.user.Gender + +data class ProfileUpdateRequest( + @SerializedName("email") val email: String, + @SerializedName("password") val password: String? = null, + @SerializedName("modifyPassword") val modifyPassword: String? = null, + @SerializedName("nickname") val nickname: String? = null, + @SerializedName("gender") val gender: Gender? = null, + @SerializedName("insertTags") val insertTags: List? = null, + @SerializedName("removeTags") val removeTags: List? = null, + @SerializedName("introduce") val introduce: String? = null, + @SerializedName("youtubeUrl") val youtubeUrl: String? = null, + @SerializedName("instagramUrl") val instagramUrl: String? = null, + @SerializedName("websiteUrl") val websiteUrl: String? = null, + @SerializedName("blogUrl") val blogUrl: String? = null, + @SerializedName("container") val container: String = "aos" +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt new file mode 100644 index 0000000..8565934 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/ProfileUpdateViewModel.kt @@ -0,0 +1,290 @@ +package kr.co.vividnext.sodalive.mypage.profile + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +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.user.Gender +import kr.co.vividnext.sodalive.user.UserRepository +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File + +class ProfileUpdateViewModel(private val repository: UserRepository) : BaseViewModel() { + + var youtubeUrl = "" + var instagramUrl = "" + var websiteUrl = "" + var blogUrl = "" + var introduce = "" + + var currentPassword = "" + var newPassword = "" + var newPasswordConfirm = "" + + val tags = mutableSetOf() + private val insertTags = mutableListOf() + private val removeTags = mutableListOf() + + private lateinit var profileResponse: ProfileResponse + + private val _userInfoLiveData = MutableLiveData() + val userInfoLiveData: LiveData + get() = _userInfoLiveData + + private val _genderLiveData = MutableLiveData() + val genderLiveData: LiveData + get() = _genderLiveData + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private val _selectedTagLiveData = MutableLiveData>() + val selectedTagLiveData: LiveData> + get() = _selectedTagLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + lateinit var getRealPathFromURI: (Uri) -> String? + + fun getUserInfo() { + compositeDisposable.add( + repository.getProfile("Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + profileResponse = it.data + tags.addAll(profileResponse.tags) + _selectedTagLiveData.postValue(profileResponse.tags) + _genderLiveData.postValue(profileResponse.gender) + _userInfoLiveData.postValue(profileResponse) + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun changeGender(gender: Gender) { + _genderLiveData.postValue(gender) + } + + fun updateProfileImage(uri: Uri, onSuccess: (String) -> Unit) { + val file = File(getRealPathFromURI(uri)) + val image = MultipartBody.Part.createFormData( + "image", + file.name, + file.asRequestBody("image/*".toMediaType()) + ) + + compositeDisposable.add( + repository.updateProfileImage(image, "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + onSuccess(it.data) + _toastLiveData.postValue("프로필 이미지가 변경되었습니다.") + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun updateProfile(onSuccess: () -> Unit) { + if ( + profileResponse.youtubeUrl != youtubeUrl || + profileResponse.instagramUrl != instagramUrl || + profileResponse.blogUrl != blogUrl || + profileResponse.websiteUrl != websiteUrl || + profileResponse.gender != _genderLiveData.value || + insertTags.isNotEmpty() || + removeTags.isNotEmpty() || + profileResponse.introduce != introduce + ) { + val request = ProfileUpdateRequest( + email = profileResponse.email, + nickname = null, + youtubeUrl = if (profileResponse.youtubeUrl != youtubeUrl) { + youtubeUrl + } else { + null + }, + instagramUrl = if (profileResponse.instagramUrl != instagramUrl) { + instagramUrl + } else { + null + }, + blogUrl = if (profileResponse.blogUrl != blogUrl) { + blogUrl + } else { + null + }, + websiteUrl = if (profileResponse.websiteUrl != websiteUrl) { + websiteUrl + } else { + null + }, + gender = if (profileResponse.gender != _genderLiveData.value) { + _genderLiveData.value + } else { + null + }, + introduce = if (profileResponse.introduce != introduce) { + introduce + } else { + null + }, + insertTags = insertTags.ifEmpty { null }, + removeTags = removeTags.ifEmpty { null } + ) + + _isLoading.value = true + + compositeDisposable.add( + repository.updateProfile(request, "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success) { + _toastLiveData.postValue( + "프로필이 변경되었습니다." + ) + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } else run { + onSuccess() + } + } + + fun updatePassword(onSuccess: () -> Unit) { + val email = SharedPreferenceManager.email + + if (currentPassword.isBlank()) { + _toastLiveData.postValue("현재 비밀번호를 입력하세요") + return + } + + if (newPassword.isBlank()) { + _toastLiveData.postValue("변경할 비밀번호를 입력하세요") + return + } + + if (newPasswordConfirm != newPassword) { + _toastLiveData.postValue("비밀번호가 일치하지 않습니다.") + return + } + + val request = ProfileUpdateRequest( + email = email, + password = currentPassword, + modifyPassword = newPassword + ) + + _isLoading.value = true + + compositeDisposable.add( + repository.updateProfile(request, "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success) { + _toastLiveData.postValue( + "비밀번호가 변경되었습니다." + ) + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun removeTag(tag: String) { + tags.remove(tag) + if (insertTags.contains(tag)) { + insertTags.remove(tag) + } else { + removeTags.add(tag) + } + _selectedTagLiveData.postValue(tags.toList()) + } + + fun addTag(tag: String) { + tags.add(tag) + if (removeTags.contains(tag)) { + removeTags.remove(tag) + } else { + insertTags.add(tag) + } + _selectedTagLiveData.postValue(tags.toList()) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/GetChangeNicknamePriceResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/GetChangeNicknamePriceResponse.kt new file mode 100644 index 0000000..90317e0 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/GetChangeNicknamePriceResponse.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.mypage.profile.nickname + +import com.google.gson.annotations.SerializedName + +data class GetChangeNicknamePriceResponse(@SerializedName("price") val price: Int) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateActivity.kt new file mode 100644 index 0000000..2543195 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateActivity.kt @@ -0,0 +1,83 @@ +package kr.co.vividnext.sodalive.mypage.profile.nickname + +import android.annotation.SuppressLint +import android.os.Bundle +import android.widget.Toast +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.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ActivityNicknameUpdateBinding +import org.koin.android.ext.android.inject +import java.util.concurrent.TimeUnit + +class NicknameUpdateActivity : BaseActivity( + ActivityNicknameUpdateBinding::inflate +) { + + private val viewModel: NicknameUpdateViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + bindData() + viewModel.getChangeNicknamePrice() + + binding.etNickname.setText(SharedPreferenceManager.nickname) + } + + override fun setupView() { + binding.toolbar.tvBack.text = "닉네임 변경" + binding.toolbar.tvBack.setOnClickListener { finish() } + + loadingDialog = LoadingDialog(this, layoutInflater) + + binding.tvCheckNickname.setOnClickListener { + viewModel.checkNickname() + } + + binding.tvChangeNickname.setOnClickListener { + viewModel.changeNickname { finish() } + } + } + + @SuppressLint("SetTextI18n") + private fun bindData() { + compositeDisposable.add( + binding.etNickname.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.isCheckedNickname = false + viewModel.nickname = it.toString() + } + ) + + viewModel.toastLiveData.observe(this) { + it?.let { + Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() + } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth) + } else { + loadingDialog.dismiss() + } + } + + viewModel.priceLiveData.observe(this) { + if (it > 0) { + binding.tvChangeNickname.text = "${it}코인으로 닉네임 변경하기" + } else { + binding.tvChangeNickname.text = "닉네임 변경하기" + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateViewModel.kt new file mode 100644 index 0000000..e68006e --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/nickname/NicknameUpdateViewModel.kt @@ -0,0 +1,142 @@ +package kr.co.vividnext.sodalive.mypage.profile.nickname + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +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.mypage.profile.ProfileUpdateRequest +import kr.co.vividnext.sodalive.user.UserRepository + +class NicknameUpdateViewModel(private val repository: UserRepository) : BaseViewModel() { + var nickname = "" + + private val _priceLiveData = MutableLiveData(0) + val priceLiveData: LiveData + get() = _priceLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + var isCheckedNickname = false + + fun getChangeNicknamePrice() { + _isLoading.value = true + + compositeDisposable.add( + repository.getChangeNicknamePrice(token = "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success && it.data != null) { + _priceLiveData.value = it.data.price + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun checkNickname() { + if (nickname.isNotBlank()) { + _isLoading.value = true + + compositeDisposable.add( + repository.checkNickname(nickname) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + isCheckedNickname = true + _toastLiveData.postValue("사용가능한 닉네임 입니다.") + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + ) + ) + } else { + _toastLiveData.postValue("닉네임을 입력하세요.") + } + } + + fun changeNickname(onSuccess: () -> Unit) { + if (isCheckedNickname) { + _isLoading.value = true + compositeDisposable.add( + repository.changeNickname( + request = ProfileUpdateRequest( + email = SharedPreferenceManager.email, + nickname = nickname + ), + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + _toastLiveData.postValue("닉네임이 변경되었습니다.") + SharedPreferenceManager.nickname = nickname + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + ) + ) + } else { + _toastLiveData.postValue("닉네임 중복체크를 해주세요.") + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/password/ModifyPasswordActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/password/ModifyPasswordActivity.kt new file mode 100644 index 0000000..a841f14 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/password/ModifyPasswordActivity.kt @@ -0,0 +1,72 @@ +package kr.co.vividnext.sodalive.mypage.profile.password + +import android.os.Bundle +import android.widget.Toast +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.databinding.ActivityModifyPasswordBinding +import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateViewModel +import org.koin.android.ext.android.inject +import java.util.concurrent.TimeUnit + +class ModifyPasswordActivity : BaseActivity( + ActivityModifyPasswordBinding::inflate +) { + + private val viewModel: ProfileUpdateViewModel by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + bindData() + } + + private fun bindData() { + compositeDisposable.add( + binding.etCurrentPassword.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.currentPassword = it.toString() + } + ) + + compositeDisposable.add( + binding.etNewPassword.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.newPassword = it.toString() + } + ) + + compositeDisposable.add( + binding.etNewPasswordConfirm.textChanges().skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe { + viewModel.newPasswordConfirm = it.toString() + } + ) + + viewModel.toastLiveData.observe(this) { + it?.let { + Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() + } + } + } + + override fun setupView() { + binding.toolbar.tvBack.text = "비밀번호 변경" + binding.toolbar.tvBack.setOnClickListener { finish() } + + binding.tvModifyPassword.setOnClickListener { + viewModel.updatePassword { finish() } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagAdapter.kt new file mode 100644 index 0000000..9a62823 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagAdapter.kt @@ -0,0 +1,76 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemLiveTagBinding +import kr.co.vividnext.sodalive.extensions.dpToPx + +class MemberTagAdapter( + private val selectedTags: Set, + private val onItemClick: (String, Boolean) -> Boolean +) : RecyclerView.Adapter() { + inner class ViewHolder( + private val binding: ItemLiveTagBinding + ) : RecyclerView.ViewHolder(binding.root) { + + private var isChecked = false + + fun bind(item: MemberTagResponse) { + if (selectedTags.contains(item.tag)) { + binding.ivTagChecked.visibility = View.VISIBLE + binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_9970ff) + isChecked = true + } else { + binding.ivTagChecked.visibility = View.GONE + binding.ivTag.background = null + isChecked = false + } + + binding.ivTag.load(item.image) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(30f.dpToPx())) + } + binding.tvTag.text = item.tag + + binding.root.setOnClickListener { + isChecked = !isChecked + + if (onItemClick(item.tag, isChecked)) { + if (isChecked) { + binding.ivTagChecked.visibility = View.VISIBLE + binding.ivTag.setBackgroundResource(R.drawable.bg_round_corner_30_9970ff) + } else { + binding.ivTagChecked.visibility = View.GONE + binding.ivTag.background = null + } + } else { + isChecked = !isChecked + } + } + } + } + + val items = mutableSetOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + ItemLiveTagBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items.toList()[position]) + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagApi.kt new file mode 100644 index 0000000..3b5601e --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagApi.kt @@ -0,0 +1,13 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import retrofit2.http.GET +import retrofit2.http.Header + +interface MemberTagApi { + @GET("/member/tag") + fun getTags( + @Header("Authorization") authHeader: String + ): Single>> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagFragment.kt new file mode 100644 index 0000000..f0f1328 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagFragment.kt @@ -0,0 +1,117 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +import android.annotation.SuppressLint +import android.app.Dialog +import android.graphics.Rect +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class MemberTagFragment( + private val selectedTags: Set, + private val onItemClick: (String, Boolean) -> Boolean +) : BottomSheetDialogFragment() { + private val viewModel: MemberTagViewModel by inject() + + private lateinit var adapter: MemberTagAdapter + private lateinit var loadingDialog: LoadingDialog + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + dialog.setOnShowListener { + val d = it as BottomSheetDialog + val bottomSheet = d.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) + if (bottomSheet != null) { + BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED + } + } + + return dialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_creator_tag, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + view.findViewById(R.id.iv_close).setOnClickListener { + dialog?.dismiss() + } + + view.findViewById(R.id.tv_select).setOnClickListener { + dialog?.dismiss() + } + + loadingDialog = LoadingDialog(requireActivity(), layoutInflater) + + setupAdapter(view) + bindData() + + viewModel.getTags() + } + + private fun setupAdapter(view: View) { + val recyclerView = view.findViewById(R.id.rv_tags) + adapter = MemberTagAdapter(selectedTags) { tag, isChecked -> + return@MemberTagAdapter onItemClick(tag, isChecked) + } + + recyclerView.setHasFixedSize(true) + recyclerView.layoutManager = GridLayoutManager(requireContext(), 4) + recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.top = 13.3f.dpToPx().toInt() + outRect.bottom = 13.3f.dpToPx().toInt() + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + } + }) + recyclerView.adapter = adapter + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindData() { + viewModel.toastLiveData.observe(viewLifecycleOwner) { + it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() } + } + + viewModel.tagLiveData.observe(viewLifecycleOwner) { + adapter.items.addAll(it) + adapter.notifyDataSetChanged() + } + + viewModel.isLoading.observe(viewLifecycleOwner) { + if (it) { + loadingDialog.show(resources.displayMetrics.widthPixels) + } else { + loadingDialog.dismiss() + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagRepository.kt new file mode 100644 index 0000000..d848b42 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagRepository.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +class MemberTagRepository(private val api: MemberTagApi) { + fun getTags(token: String) = api.getTags(authHeader = token) +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagResponse.kt new file mode 100644 index 0000000..6ad3ae1 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +import com.google.gson.annotations.SerializedName + +data class MemberTagResponse( + @SerializedName("id") val id: Long, + @SerializedName("tag") val tag: String, + @SerializedName("image") val image: String +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagViewModel.kt new file mode 100644 index 0000000..e5d7b80 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagViewModel.kt @@ -0,0 +1,53 @@ +package kr.co.vividnext.sodalive.mypage.profile.tag + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +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 + +class MemberTagViewModel(private val repository: MemberTagRepository) : BaseViewModel() { + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private val _tagLiveData = MutableLiveData>() + val tagLiveData: LiveData> + get() = _tagLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + fun getTags() { + _isLoading.value = true + compositeDisposable.add( + repository.getTags("Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + _tagLiveData.postValue(it.data!!) + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } +} 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 1207aa7..bc02205 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 @@ -6,6 +6,9 @@ import kr.co.vividnext.sodalive.explorer.profile.MemberBlockRequest import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest import kr.co.vividnext.sodalive.mypage.MyPageResponse +import kr.co.vividnext.sodalive.mypage.profile.ProfileResponse +import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateRequest +import kr.co.vividnext.sodalive.mypage.profile.nickname.GetChangeNicknamePriceResponse import kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponse import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.settings.signout.SignOutRequest @@ -101,4 +104,37 @@ interface UserApi { @POST("/member/logout/all") fun logoutAll(@Header("Authorization") authHeader: String): Single> + + @GET("/member/change/nickname/price") + fun getChangeNicknamePrice( + @Header("Authorization") authHeader: String + ): Single> + + @GET("/member/check/nickname") + fun checkNickname(@Query("nickname") nickname: String): Single> + + @PUT("/member/change/nickname") + fun changeNickname( + @Body request: ProfileUpdateRequest, + @Header("Authorization") authHeader: String + ): Single> + + @GET("/member") + fun getMyProfile( + @Query("container") container: String = "aos", + @Header("Authorization") authHeader: String + ): Single> + + @PUT("/member") + fun updateProfile( + @Body request: ProfileUpdateRequest, + @Header("Authorization") authHeader: String + ): Single> + + @Multipart + @POST("/member/image") + fun updateProfileImage( + @Part multipartFile: MultipartBody.Part, + @Header("Authorization") authHeader: String + ): Single> } 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 a3d3fbe..80afb2a 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 @@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.explorer.profile.MemberBlockRequest import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest import kr.co.vividnext.sodalive.mypage.MyPageResponse +import kr.co.vividnext.sodalive.mypage.profile.ProfileResponse +import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateRequest import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.login.LoginRequest @@ -79,4 +81,31 @@ class UserRepository(private val userApi: UserApi) { fun logout(token: String) = userApi.logout(authHeader = token) fun logoutAllDevice(token: String) = userApi.logoutAll(authHeader = token) + + fun getChangeNicknamePrice(token: String) = userApi.getChangeNicknamePrice(authHeader = token) + + fun checkNickname(nickname: String) = userApi.checkNickname(nickname) + + fun changeNickname( + request: ProfileUpdateRequest, + token: String + ) = userApi.changeNickname(request = request, authHeader = token) + + fun updateProfileImage( + multipartFile: MultipartBody.Part, + token: String + ): Single> { + return userApi.updateProfileImage(multipartFile, authHeader = token) + } + + fun updateProfile( + request: ProfileUpdateRequest, + token: String + ): Single> { + return userApi.updateProfile(request, authHeader = token) + } + + fun getProfile(token: String): Single> { + return userApi.getMyProfile(authHeader = token) + } } diff --git a/app/src/main/res/drawable/bg_round_corner_30_9970ff.xml b/app/src/main/res/drawable/bg_round_corner_30_9970ff.xml new file mode 100644 index 0000000..a27d720 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_30_9970ff.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_8_9970ff.xml b/app/src/main/res/drawable/bg_round_corner_8_9970ff.xml new file mode 100644 index 0000000..0ba87ba --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_8_9970ff.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_modify_password.xml b/app/src/main/res/layout/activity_modify_password.xml new file mode 100644 index 0000000..50057c7 --- /dev/null +++ b/app/src/main/res/layout/activity_modify_password.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_nickname_update.xml b/app/src/main/res/layout/activity_nickname_update.xml new file mode 100644 index 0000000..ad4c6e6 --- /dev/null +++ b/app/src/main/res/layout/activity_nickname_update.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_profile_update.xml b/app/src/main/res/layout/activity_profile_update.xml new file mode 100644 index 0000000..b195cc3 --- /dev/null +++ b/app/src/main/res/layout/activity_profile_update.xml @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_creator_tag.xml b/app/src/main/res/layout/fragment_creator_tag.xml new file mode 100644 index 0000000..82fbc80 --- /dev/null +++ b/app/src/main/res/layout/fragment_creator_tag.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + +