diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 3bcb6b3b..879b3596 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -9,12 +9,6 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 17e911dc..afee0081 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + - + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5f4bd501..0dbebbaa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,8 +10,8 @@ plugins { } android { - compileSdk = Constants.compileSdk + compileSdk = Constants.compileSdk defaultConfig { applicationId = Constants.packageName minSdk = Constants.minSdk diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0394a578..d0f625eb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,9 +5,7 @@ - + @@ -24,9 +22,12 @@ android:theme="@style/Theme.JJBAKSAAOS" tools:replace="android:appComponentFactory" tools:targetApi="31"> + + android:exported="true"> @@ -65,8 +66,7 @@ android:exported="false" /> - + android:exported="false"> diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/JjbaksaApp.kt b/app/src/main/java/com/jjbaksa/jjbaksa/JjbaksaApp.kt index ad513b26..ba426472 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/JjbaksaApp.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/JjbaksaApp.kt @@ -5,7 +5,13 @@ import com.kakao.sdk.common.KakaoSdk import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import com.jjbaksa.data.database.PreferenceKeys +import com.jjbaksa.data.database.userDataStore +import com.jjbaksa.jjbaksa.util.MyInfo import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch @HiltAndroidApp class JjbaksaApp : Application() { @@ -17,6 +23,9 @@ class JjbaksaApp : Application() { super.onCreate() KakaoSdk.init(this, BuildConfig.kakao_native_app_key) instance = this + GlobalScope.launch { + MyInfo.id = appContext.userDataStore.data.first()[PreferenceKeys.NICKNAME] ?: "" + } } /** diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/base/BaseActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/base/BaseActivity.kt index 6e9c2110..c8918a4f 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/base/BaseActivity.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/base/BaseActivity.kt @@ -30,10 +30,12 @@ abstract class BaseActivity : AppCompatActivity() { abstract fun initView() abstract fun subscribe() abstract fun initEvent() +// abstract fun initData() open fun initState() { initView() initEvent() subscribe() +// initData() } fun isPermissionGranted(perm: String): Boolean { diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/dialog/ConfirmDialog.kt b/app/src/main/java/com/jjbaksa/jjbaksa/dialog/ConfirmDialog.kt index 9f576ba5..61c8ef42 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/dialog/ConfirmDialog.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/dialog/ConfirmDialog.kt @@ -1,6 +1,7 @@ package com.jjbaksa.jjbaksa.dialog import android.app.Dialog +import android.graphics.Color import android.view.View import com.jjbaksa.jjbaksa.R import com.jjbaksa.jjbaksa.base.BaseDialogFragment @@ -11,6 +12,7 @@ class ConfirmDialog( val msg: String? = null, val confirmText: String, val confirmClick: (Dialog) -> Unit, + val titleColor: String = "#ff7f23", val isCancel: Boolean = true ) : BaseDialogFragment() { override val layoutResId: Int @@ -20,6 +22,7 @@ class ConfirmDialog( isCancelable = isCancel with(binding) { confirmDialogTitle.text = title + confirmDialogTitle.setTextColor(Color.parseColor(titleColor)) if (msg == null) { confirmDialogMsg.visibility = View.GONE } else { diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/dialog/MyPageBottomSheetDialog.kt b/app/src/main/java/com/jjbaksa/jjbaksa/dialog/MyPageBottomSheetDialog.kt new file mode 100644 index 00000000..de89b7f8 --- /dev/null +++ b/app/src/main/java/com/jjbaksa/jjbaksa/dialog/MyPageBottomSheetDialog.kt @@ -0,0 +1,112 @@ +package com.jjbaksa.jjbaksa.dialog + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.activityViewModels +import com.jjbaksa.jjbaksa.R +import com.jjbaksa.jjbaksa.base.BaseBottomSheetDialogFragment +import com.jjbaksa.jjbaksa.databinding.DialogMypageBinding +import com.jjbaksa.jjbaksa.ui.mainpage.mypage.viewmodel.MyPageViewModel +import dagger.hilt.android.AndroidEntryPoint +import coil.load +import coil.transform.CircleCropTransformation +import com.example.imageselector.gallery.GalleryActivity +import com.jjbaksa.jjbaksa.util.hasPermission + +@AndroidEntryPoint +class MyPageBottomSheetDialog : BaseBottomSheetDialogFragment() { + override val layoutResId: Int + get() = R.layout.dialog_mypage + private val viewModel: MyPageViewModel by activityViewModels() + + private val galleryActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + val images = it.data!!.getStringArrayListExtra("images")!! + viewModel.setLoadImage(images[0]) + binding.profileImageView.load(images[0]) { + transformations(CircleCropTransformation()) + } + } + } + private val requestPermissions = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { + if (it) { + val intent = Intent(requireContext(), GalleryActivity::class.java) + intent.putExtra("limit", 1) + galleryActivityLauncher.launch(intent) + } else { + } + } + + override fun initView(view: View) { + binding.vm = viewModel + viewModel.getUserProfile() + this.heightPercent = 0.55f + this.setLayoutMaxHeight(view) + } + + override fun initEvent() { + confirmProfile() + cancelProfile() + loadProfileImage() + setTextLength() + observeData() + } + + private fun confirmProfile() { + binding.confirmButton.setOnClickListener { + if (viewModel.loadImage.value.isNullOrEmpty() || viewModel.textLength.value == "0") { + // todo:: empty profile image or nickname + } else { + viewModel.uploadProfileImgAndNickname( + viewModel.loadImage.value.toString(), + binding.profileNicknameEditText.text.toString() + ) + } + } + } + + private fun cancelProfile() { + binding.cancelButton.setOnClickListener { + dismiss() + } + } + + private fun loadProfileImage() { + binding.addProfileImage.setOnClickListener { + if (requireContext().hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + val intent = Intent(requireContext(), GalleryActivity::class.java) + intent.putExtra("limit", 1) + galleryActivityLauncher.launch(intent) + } else { + requestPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } + } + + private fun setTextLength() { + binding.profileNicknameEditText.addTextChangedListener { + viewModel.setTextLength(it?.length.toString()) + } + } + + private fun observeData() { + viewModel.textLength.observe(viewLifecycleOwner) { + binding.textLengthCountTextView.text = getString(R.string.text_length, it) + } + viewModel.isResult.observe(viewLifecycleOwner) { + if (it) dismiss() + } + } + + override fun subscribe() { + } + + override fun initData() { + } +} diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/ChangePasswordActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/ChangePasswordActivity.kt index 180755f1..3515c0f9 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/ChangePasswordActivity.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/ChangePasswordActivity.kt @@ -1,6 +1,8 @@ package com.jjbaksa.jjbaksa.ui.changepassword +import android.graphics.drawable.Drawable import androidx.activity.viewModels +import androidx.core.content.ContextCompat import com.jjbaksa.jjbaksa.R import com.jjbaksa.jjbaksa.base.BaseActivity import com.jjbaksa.jjbaksa.databinding.ActivityChangePasswordBinding @@ -14,8 +16,51 @@ class ChangePasswordActivity : BaseActivity() { get() = R.layout.activity_change_password private val viewModel: ChangePasswordViewModel by viewModels() + var currentPasswordText = "" + var newPasswordText = "" + var checkPasswordText = "" + var isFailedCurrentPassword = false + var isFailedNewPassword = false + override fun initView() { binding.jjAppBarContainer.setOnClickListener { finish() } + observeData() + } + + private fun observeData() { + viewModel.currentPasswordState.observe(this) { + isFailedCurrentPassword = !it?.isSuccess!! + if (isFailedCurrentPassword) { + binding.currentPasswordEditText.editTextBackground = failButtonBackground() + showSnackBar(it?.msg.toString(), getString(R.string.cancel)) + } else { + if (newPasswordText != checkPasswordText) { + showSnackBar( + getString(R.string.not_match_new_password), + getString(R.string.cancel) + ) + binding.newPasswordEditText.editTextBackground = failButtonBackground() + binding.checkNewPasswordEditText.editTextBackground = failButtonBackground() + isFailedNewPassword = true + } else { + viewModel.setNewPassword(binding.newPasswordEditText.editTextText) + } + } + } + viewModel.newPasswordState.observe(this) { + if (it?.isSuccess!!) { + setConfirmDialog() + } else { + showSnackBar(it.msg.toString(), getString(R.string.cancel)) + binding.newPasswordEditText.editTextBackground = failButtonBackground() + binding.checkNewPasswordEditText.editTextBackground = failButtonBackground() + isFailedNewPassword = true + } + } + viewModel.isEnableButton.observe(this) { + binding.changePasswordButton.isEnabled = it + binding.changePasswordButton.isSelected = it + } } override fun subscribe() { @@ -25,29 +70,80 @@ class ChangePasswordActivity : BaseActivity() { setCurrentPassword() setNewPassword() setCheckNewPassword() + setConfirmButton() } private fun setCurrentPassword() { binding.currentPasswordEditText.also { it.setOnFocusChangeListener { _, _ -> } - it.addTextChangedListener { currentPassword -> } + it.addTextChangedListener { currentPassword -> + if (isFailedCurrentPassword) { + it.editTextBackground = comeBackButtonBackground() + isFailedCurrentPassword = false + } + currentPasswordText = currentPassword.toString() + setEditTextState() + } } } private fun setNewPassword() { binding.newPasswordEditText.also { it.setOnFocusChangeListener { _, _ -> } - it.addTextChangedListener { newPassword -> } + it.addTextChangedListener { newPassword -> + if (isFailedNewPassword) { + it.editTextBackground = comeBackButtonBackground() + binding.checkNewPasswordEditText.editTextBackground = comeBackButtonBackground() + isFailedNewPassword = false + } + newPasswordText = newPassword.toString() + setEditTextState() + } } } private fun setCheckNewPassword() { binding.checkNewPasswordEditText.also { it.setOnFocusChangeListener { _, _ -> } - it.addTextChangedListener { checkNewPassword -> } + it.addTextChangedListener { checkNewPassword -> + if (isFailedNewPassword) { + it.editTextBackground = comeBackButtonBackground() + binding.newPasswordEditText.editTextBackground = comeBackButtonBackground() + isFailedNewPassword = false + } + checkPasswordText = checkNewPassword.toString() + setEditTextState() + } } } + private fun isNotEmptyEditText(): Boolean = + currentPasswordText.isNotEmpty() && newPasswordText.isNotEmpty() && checkPasswordText.isNotEmpty() + + private fun setEditTextState() { + if (isNotEmptyEditText()) { + viewModel.setEnabledButton(true) + } else { + viewModel.setEnabledButton(false) + } + } + + private fun setConfirmButton() { + binding.changePasswordButton.setOnClickListener { + viewModel.checkPassword(binding.currentPasswordEditText.editTextText) + } + } + + private fun failButtonBackground(): Drawable? = ContextCompat.getDrawable( + this, + R.drawable.shape_rect_eeeeee_solid_radius_100_stroke_ff7f23 + ) + + private fun comeBackButtonBackground(): Drawable? = ContextCompat.getDrawable( + this, + R.drawable.shape_rect_eeeeee_solid_radius_100_padding_7_11_11_8 + ) + private fun setConfirmDialog() { ConfirmDialog( getString(R.string.success_change_password), diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/viewmodel/ChangePasswordViewModel.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/viewmodel/ChangePasswordViewModel.kt index f290b421..a926b755 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/viewmodel/ChangePasswordViewModel.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/changepassword/viewmodel/ChangePasswordViewModel.kt @@ -1,8 +1,11 @@ package com.jjbaksa.jjbaksa.ui.changepassword.viewmodel -import androidx.lifecycle.ViewModel +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.jjbaksa.domain.repository.UserRepository +import com.jjbaksa.domain.resp.user.FormatResp +import com.jjbaksa.jjbaksa.base.BaseViewModel +import com.jjbaksa.jjbaksa.util.SingleLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -10,10 +13,28 @@ import javax.inject.Inject @HiltViewModel class ChangePasswordViewModel @Inject constructor( val repository: UserRepository -) : ViewModel() { +) : BaseViewModel() { + var isEnableButton = MutableLiveData(false) + + private val _currentPasswordState = SingleLiveEvent() + val currentPasswordState: SingleLiveEvent get() = _currentPasswordState + + private val _newPasswordState = SingleLiveEvent() + val newPasswordState: SingleLiveEvent get() = _newPasswordState + fun checkPassword(password: String) { - viewModelScope.launch { - repository.checkPassword(password) + viewModelScope.launch(ceh) { + _currentPasswordState.value = repository.checkPassword(password) + } + } + + fun setNewPassword(password: String) { + viewModelScope.launch(ceh) { + _newPasswordState.value = repository.setNewPassword(password) } } + + fun setEnabledButton(enable: Boolean) { + isEnableButton.value = enable + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdFragment.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdFragment.kt index 127701ff..84d39274 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdFragment.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdFragment.kt @@ -42,7 +42,7 @@ class FindIdFragment : BaseFragment() { private fun sendVerificationCode() { binding.buttonFindIdSendToVerificationCode.setOnClickListener { - KeyboardProvider().hideKeyboard(requireContext(), binding.editTextFindIdToEmail) + KeyboardProvider(requireContext()).hideKeyboard(binding.editTextFindIdToEmail) viewModel.getAuthEmail(binding.editTextFindIdToEmail.text.toString()) } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdInputVerificationCodeFragment.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdInputVerificationCodeFragment.kt index a1b5ff25..5d7625a3 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdInputVerificationCodeFragment.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findid/FindIdInputVerificationCodeFragment.kt @@ -68,14 +68,14 @@ class FindIdInputVerificationCodeFragment : BaseFragment() { findNavController().navigate(R.id.action_nav_find_password_to_nav_find_password_input_code) } else { outlineState = true - KeyboardProvider().hideKeyboard(requireContext(), binding.inputEmailEditText) + KeyboardProvider(requireContext()).hideKeyboard(binding.inputEmailEditText) if (it.msg.toString().contains(USER)) { showSnackBar(getString(R.string.fail_id)) changeEditTextOutlineColor(binding.inputIdEditText) diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findpassword/FindPasswordInputCodeFragment.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findpassword/FindPasswordInputCodeFragment.kt index a3c3bca0..2b7793bb 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/findpassword/FindPasswordInputCodeFragment.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/findpassword/FindPasswordInputCodeFragment.kt @@ -58,7 +58,7 @@ class FindPasswordInputCodeFragment : BaseFragment ContextCompat.getDrawable(requireContext(), R.drawable.shape_rect_eeeeee_solid_radius_100_stroke_ff7f23) binding.inputCheckPasswordEditText.editTextBackground = ContextCompat.getDrawable(requireContext(), R.drawable.shape_rect_eeeeee_solid_radius_100_stroke_ff7f23) - KeyboardProvider().hideKeyboard(requireContext(), binding.root) + KeyboardProvider(requireContext()).hideKeyboard(binding.root) } companion object { diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginActivity.kt index bc8f8cb9..9923ea2a 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginActivity.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginActivity.kt @@ -28,11 +28,11 @@ class LoginActivity : BaseActivity() { override fun subscribe() { with(viewModel) { - loginState.observe(this@LoginActivity) { if (it != null) { if (it.isSuccess) { goToMainActivity() + viewModel.loadUserMe() } else { if (it.erroMessage.isNotEmpty()) { showSnackBar(it.erroMessage, getString(R.string.cancel)) diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginViewModel.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginViewModel.kt index 71c92ffd..62ed1a66 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/login/LoginViewModel.kt @@ -22,9 +22,6 @@ class LoginViewModel @Inject constructor( private val _loginState = SingleLiveEvent() val loginState: SingleLiveEvent get() = _loginState - private val _autoLoginState = SingleLiveEvent() - val autoLoginState: SingleLiveEvent get() = _autoLoginState - fun login(isCheckedSwitch: Boolean) { if (!TextUtils.isEmpty(account.value) && !TextUtils.isEmpty(password.value)) { viewModelScope.launch(ceh) { @@ -35,15 +32,9 @@ class LoginViewModel @Inject constructor( } } -// fun getAutoLoginFlag() { -// viewModelScope.launch(ceh) { -// -// if (repository.getAutoLoginFlag()) { -// isAutoLogin.value = true -// account.value = repository.getAccount() -// password.value = repository.getPasswrod() -// login() -// } -// } -// } + fun loadUserMe() { + viewModelScope.launch { + repository.me() + } + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/NaviMyPageFragment.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/NaviMyPageFragment.kt index 0729d09e..7cf35196 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/NaviMyPageFragment.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/NaviMyPageFragment.kt @@ -3,11 +3,16 @@ package com.jjbaksa.jjbaksa.ui.mainpage.mypage import android.content.Intent import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import coil.load +import coil.transform.CircleCropTransformation import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import com.jjbaksa.jjbaksa.R import com.jjbaksa.jjbaksa.base.BaseFragment import com.jjbaksa.jjbaksa.databinding.FragmentNaviMyPageBinding +import com.jjbaksa.jjbaksa.dialog.MyPageBottomSheetDialog +import com.jjbaksa.jjbaksa.ui.mainpage.mypage.viewmodel.MyPageViewModel import com.jjbaksa.jjbaksa.ui.setting.SettingActivity import com.jjbaksa.jjbaksa.util.setExtendView import dagger.hilt.android.AndroidEntryPoint @@ -16,18 +21,43 @@ import dagger.hilt.android.AndroidEntryPoint class NaviMyPageFragment : BaseFragment() { override val layoutId: Int get() = R.layout.fragment_navi_my_page + private val viewModel: MyPageViewModel by activityViewModels() + private val reviewFragment by lazy { ReviewFragment() } private val bookmarkFragment by lazy { BookmarkFragment() } - private val settingResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - // Handle SettingActivity result - } + private val settingResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + // Handle SettingActivity result + } override fun initView() { requireActivity().setExtendView(binding.myPageConstraintLayout) initFragment(reviewFragment) setTabLayout() + binding.vm = viewModel + binding.lifecycleOwner = this + viewModel.getUserProfile() + observeData() } + + private fun observeData() { + viewModel.nickname.observe(viewLifecycleOwner) { + binding.profileNameTextView.text = it + } + viewModel.profileImage.observe(viewLifecycleOwner) { + if (it.isEmpty()) { + binding.profileImageView.load(R.drawable.baseline_supervised_user_circle_24) { + transformations(CircleCropTransformation()) + } + } else { + binding.profileImageView.load(it) { + transformations(CircleCropTransformation()) + } + } + } + } + private fun initFragment(fragment: Fragment) { parentFragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment) @@ -42,16 +72,20 @@ class NaviMyPageFragment : BaseFragment() { 1 -> initFragment(bookmarkFragment) } } + override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} }) } override fun initEvent() { - setSettingActivity() + onClickSettingImage() + binding.profileImageView.setOnClickListener { + MyPageBottomSheetDialog().show(parentFragmentManager, MY_PAGE_DIALOG_TAG) + } } - private fun setSettingActivity() { + private fun onClickSettingImage() { binding.settingImageButton.setOnClickListener { settingResult.launch(Intent(requireContext(), SettingActivity::class.java)) } @@ -59,4 +93,8 @@ class NaviMyPageFragment : BaseFragment() { override fun subscribe() { } + + companion object { + const val MY_PAGE_DIALOG_TAG = "MY_PAGE_DIALOG_TAG" + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/viewmodel/MyPageViewModel.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/viewmodel/MyPageViewModel.kt index 357e90e2..dbade065 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/viewmodel/MyPageViewModel.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/mypage/viewmodel/MyPageViewModel.kt @@ -1,11 +1,59 @@ package com.jjbaksa.jjbaksa.ui.mainpage.mypage.viewmodel -import androidx.lifecycle.ViewModel -import com.jjbaksa.domain.repository.HomeRepository +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.jjbaksa.domain.base.RespResult +import com.jjbaksa.domain.repository.UserRepository +import com.jjbaksa.jjbaksa.base.BaseViewModel +import com.jjbaksa.jjbaksa.util.SingleLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( - val repository: HomeRepository -) : ViewModel() + val repository: UserRepository +) : BaseViewModel() { + val account = MutableLiveData("") + val nickname = MutableLiveData("") + val profileFollowers = MutableLiveData(0) + val profileImage = MutableLiveData("") + val textLength = MutableLiveData("") + + private val _loadImage = SingleLiveEvent() + val loadImage: LiveData get() = _loadImage + + private val _isResult = SingleLiveEvent() + val isResult: LiveData get() = _isResult + + fun getUserProfile() { + account.value = repository.getAccount() + nickname.value = repository.getNickname() + profileFollowers.value = repository.getFollowers() + profileImage.value = repository.getProfileImage() + textLength.value = repository.getNickname().length.toString() + } + + fun setTextLength(length: String) { + textLength.value = length + } + + fun setLoadImage(image: String) { + _loadImage.value = image + } + + fun uploadProfileImgAndNickname(image: String, newNickname: String) { + viewModelScope.launch(ceh) { + val response = repository.editUserProfileImage(image) + if (response == RespResult.Success(true)) { + profileImage.value = repository.getProfileImage() + val nicknameResponse = repository.setNewNickname(newNickname) + if (nicknameResponse.isSuccess) { + nickname.value = repository.getNickname() + _isResult.value = true + } + } + } + } +} diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/viewmodel/HomeViewModel.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/viewmodel/HomeViewModel.kt index 67e8d059..01a48883 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/mainpage/viewmodel/HomeViewModel.kt @@ -2,7 +2,7 @@ package com.jjbaksa.jjbaksa.ui.mainpage.viewmodel import androidx.lifecycle.viewModelScope import com.jjbaksa.domain.model.mainpage.UserLocation -import com.jjbaksa.domain.repository.HomeRepository +import com.jjbaksa.domain.repository.UserRepository import com.jjbaksa.jjbaksa.base.BaseViewModel import com.jjbaksa.jjbaksa.ui.mainpage.sub.FusedLocationProvider import com.jjbaksa.jjbaksa.util.SingleLiveEvent @@ -12,7 +12,7 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - private val repository: HomeRepository + private val repository: UserRepository ) : BaseViewModel() { lateinit var fusedLocationProvider: FusedLocationProvider @@ -30,4 +30,10 @@ class HomeViewModel @Inject constructor( fusedLocationProvider.startLocationUpdates() } } + + fun getUserMe() { + viewModelScope.launch { + repository.me() + } + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/setting/SettingActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/setting/SettingActivity.kt index 7d855f9e..58208493 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/setting/SettingActivity.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/setting/SettingActivity.kt @@ -7,6 +7,7 @@ import com.jjbaksa.jjbaksa.base.BaseActivity import com.jjbaksa.jjbaksa.databinding.ActivitySettingBinding import com.jjbaksa.jjbaksa.ui.changepassword.ChangePasswordActivity import com.jjbaksa.jjbaksa.ui.setting.viewmodel.SettingViewModel +import com.jjbaksa.jjbaksa.ui.withdrawal.WithdrawalActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -33,9 +34,12 @@ class SettingActivity : BaseActivity() { fun goToChangePassword() { startActivity(Intent(this, ChangePasswordActivity::class.java)) } - fun goToPrivacyPolicy() { } - fun goToNotice() { } - fun goToInquiry() { } - fun logout() { } - fun withdraw() {} + + fun goToPrivacyPolicy() {} + fun goToNotice() {} + fun goToInquiry() {} + fun logout() {} + fun withdraw() { + startActivity(Intent(this, WithdrawalActivity::class.java)) + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/splash/SplashActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/splash/SplashActivity.kt index 2b385a3a..23733661 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/ui/splash/SplashActivity.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/splash/SplashActivity.kt @@ -28,17 +28,20 @@ class SplashActivity : BaseActivity() { // MainPageActivity Result Handle } - override fun initView() { + override fun subscribe() { observeData() } - - override fun subscribe() { + override fun initView() { + viewModel.getAutoLogin() } override fun initEvent() { - viewModel.getAutoLogin() } +// override fun initData() { +// viewModel.getAutoLogin() +// } + private fun observeData() { viewModel.autoLogin.observe(this) { isLogin -> if (isLogin) { @@ -59,6 +62,7 @@ class SplashActivity : BaseActivity() { finish() } } + is RespResult.Error -> { showSnackBar(it.errorType.errorMessage, getString(R.string.cancel)) goToLoginActivity() @@ -71,6 +75,7 @@ class SplashActivity : BaseActivity() { private fun goToLoginActivity() { loginResult.launch(Intent(this, LoginActivity::class.java)) } + private fun goToMainActivity() { mainResult.launch(Intent(this, MainPageActivity::class.java)) } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/WithdrawalActivity.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/WithdrawalActivity.kt new file mode 100644 index 00000000..e95135b5 --- /dev/null +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/WithdrawalActivity.kt @@ -0,0 +1,139 @@ +package com.jjbaksa.jjbaksa.ui.withdrawal + +import android.content.Intent +import androidx.activity.viewModels +import androidx.core.app.ActivityCompat +import androidx.core.widget.addTextChangedListener +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq +import com.jjbaksa.jjbaksa.R +import com.jjbaksa.jjbaksa.base.BaseActivity +import com.jjbaksa.jjbaksa.databinding.ActivityWithdrawalBinding +import com.jjbaksa.jjbaksa.dialog.ConfirmDialog +import com.jjbaksa.jjbaksa.ui.login.LoginActivity +import com.jjbaksa.jjbaksa.ui.withdrawal.viewmodel.WithdrawalViewModel +import com.jjbaksa.jjbaksa.util.KeyboardProvider +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class WithdrawalActivity : BaseActivity() { + override val layoutId: Int + get() = R.layout.activity_withdrawal + private val viewModel: WithdrawalViewModel by viewModels() + + override fun initView() { + KeyboardProvider(this).inputKeyboardResize(window, binding.root) + binding.vm = viewModel + binding.lifecycleOwner = this + viewModel.getNickname() + } + + override fun subscribe() { + observeData() + } + + private fun observeData() { + viewModel.saveWithdrawalReasonState.observe(this) { + if (it.isSuccess) { + viewModel.withdraw() + } else { + showSnackBar(it.message.toString(), getString(R.string.close)) + } + } + viewModel.isWithdrawUser.observe(this) { + if (it) { + showCompleteWithdrawalDialog() + } + } + } + + override fun initEvent() { + binding.jjAppBarContainer.setOnClickListener { finish() } + withdrawal() + setWithdrawalReason() + setFocusInputField() + setInputField() + } + + private fun withdrawal() { + binding.withdrawalButton.setOnClickListener { + if (viewModel.reason.value?.isNotEmpty()!! && binding.inputEditTextField.text?.isNotEmpty()!!) { + val withdrawalReasonReq = WithdrawalReasonReq( + viewModel.reason.value.toString(), + binding.inputEditTextField.text.toString() + ) + viewModel.saveWithdrawalReason(withdrawalReasonReq) + } + } + } + + private fun setWithdrawalReason() { + binding.reasonWithdrawalRadioGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.reason_withdrawal_radio_button_1 -> { + viewModel.setWithdrawalReason(binding.reasonWithdrawalRadioButton1.text.toString()) + } + + R.id.reason_withdrawal_radio_button_2 -> { + viewModel.setWithdrawalReason(binding.reasonWithdrawalRadioButton2.text.toString()) + } + + R.id.reason_withdrawal_radio_button_3 -> { + viewModel.setWithdrawalReason(binding.reasonWithdrawalRadioButton3.text.toString()) + } + + R.id.reason_withdrawal_radio_button_4 -> { + viewModel.setWithdrawalReason(binding.reasonWithdrawalRadioButton4.text.toString()) + } + + R.id.reason_withdrawal_radio_button_5 -> { + viewModel.setWithdrawalReason(binding.reasonWithdrawalRadioButton5.text.toString()) + } + } + } + } + + private fun setFocusInputField() { + binding.inputEditTextField.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + if (binding.inputEditTextField.text.toString() == getString(R.string.free_write)) { + binding.inputEditTextField.text?.clear() + } + } + } + } + + private fun setInputField() { + binding.inputEditTextField.addTextChangedListener { + viewModel.setInputTextLength(it?.length.toString()) + + if (it?.length == 150) { + viewModel.setMaxInputTextLength(true) + } else { + viewModel.setMaxInputTextLength(false) + } + + if (!it.isNullOrEmpty()) { + viewModel.setIsEnabled(true) + } else { + viewModel.setIsEnabled(false) + } + } + } + + private fun showCompleteWithdrawalDialog() { + ConfirmDialog( + getString(R.string.complete_withdrawal), + getString(R.string.complete_withdrawal_content), + getString(R.string.close), + { + ActivityCompat.finishAffinity(this) + startActivity(Intent(this, LoginActivity::class.java)) + }, + "#222222" + ).show(supportFragmentManager, WITHDRAWAL_DIALOG_TAG) + } + + companion object { + const val WITHDRAWAL_DIALOG_TAG = "WITHDRAWAL_DIALOG_TAG" + } +} diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/viewmodel/WithdrawalViewModel.kt b/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/viewmodel/WithdrawalViewModel.kt new file mode 100644 index 00000000..e02a0d04 --- /dev/null +++ b/app/src/main/java/com/jjbaksa/jjbaksa/ui/withdrawal/viewmodel/WithdrawalViewModel.kt @@ -0,0 +1,86 @@ +package com.jjbaksa.jjbaksa.ui.withdrawal.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jjbaksa.domain.base.RespResult +import com.jjbaksa.domain.repository.UserRepository +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq +import com.jjbaksa.domain.resp.user.WithdrawalReasonResp +import com.jjbaksa.jjbaksa.util.SingleLiveEvent +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WithdrawalViewModel @Inject constructor( + val repository: UserRepository +) : ViewModel() { + val userNickname = MutableLiveData("") + private val _isEnabled = SingleLiveEvent() + val isEnabled: LiveData get() = _isEnabled + val inputTextLength = MutableLiveData("0") + private val _maxInputTextLength = SingleLiveEvent() + val maxInputTextLength: LiveData get() = _maxInputTextLength + val reason = MutableLiveData("") + + private val _saveWithdrawalReasonState = MutableLiveData() + val saveWithdrawalReasonState: LiveData get() = _saveWithdrawalReasonState + private val _isWithdrawUser = MutableLiveData() + val isWithdrawUser: LiveData get() = _isWithdrawUser + + fun getNickname() { + userNickname.value = repository.getNickname() + } + + fun setIsEnabled(enabled: Boolean) { + _isEnabled.value = enabled + } + + fun setInputTextLength(length: String) { + inputTextLength.value = length + } + + fun setMaxInputTextLength(maxLength: Boolean) { + _maxInputTextLength.value = maxLength + } + + fun setWithdrawalReason(radioReason: String) { + reason.value = radioReason + } + + fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq) { + viewModelScope.launch { + runCatching { + repository.saveWithdrawalReason(withdrawalReason) + }.onSuccess { result -> + when (result) { + is RespResult.Success -> { + _saveWithdrawalReasonState.value = WithdrawalReasonResp(result.data, null) + } + is RespResult.Error -> { + _saveWithdrawalReasonState.value = WithdrawalReasonResp(false, result.errorType.errorMessage) + } + } + }.onFailure { } + } + } + + fun withdraw() { + viewModelScope.launch { + runCatching { + repository.deleteUser() + }.onSuccess { + when (it) { + is RespResult.Success -> { + _isWithdrawUser.value = it.data!! + } + is RespResult.Error -> { + _isWithdrawUser.value = false + } + } + }.onFailure { } + } + } +} diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/util/KeyboardProvider.kt b/app/src/main/java/com/jjbaksa/jjbaksa/util/KeyboardProvider.kt index 7c2862bd..3420fae1 100644 --- a/app/src/main/java/com/jjbaksa/jjbaksa/util/KeyboardProvider.kt +++ b/app/src/main/java/com/jjbaksa/jjbaksa/util/KeyboardProvider.kt @@ -1,12 +1,33 @@ package com.jjbaksa.jjbaksa.util import android.content.Context +import android.os.Build import android.view.View +import android.view.Window +import android.view.WindowInsets +import android.view.WindowManager import android.view.inputmethod.InputMethodManager -class KeyboardProvider() { - fun hideKeyboard(context: Context, focusItem: View) { +class KeyboardProvider(val context: Context) { + fun hideKeyboard(focusItem: View) { val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputManager.hideSoftInputFromWindow(focusItem.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) } + + fun inputKeyboardResize(window: Window, view: View) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.setDecorFitsSystemWindows(false) + view.setOnApplyWindowInsetsListener { _, insets -> + val topInset = insets.getInsets(WindowInsets.Type.statusBars()).top + val imeHeight = insets.getInsets(WindowInsets.Type.ime()).bottom + val navigationHeight = insets.getInsets(WindowInsets.Type.navigationBars()).bottom + val bottomInset = if (imeHeight == 0) navigationHeight else imeHeight + + view.setPadding(0, topInset, 0, bottomInset) + insets + } + } else { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + } + } } diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/util/MyInfo.kt b/app/src/main/java/com/jjbaksa/jjbaksa/util/MyInfo.kt new file mode 100644 index 00000000..635ff155 --- /dev/null +++ b/app/src/main/java/com/jjbaksa/jjbaksa/util/MyInfo.kt @@ -0,0 +1,5 @@ +package com.jjbaksa.jjbaksa.util + +object MyInfo { + var id: String = "" +} diff --git a/app/src/main/java/com/jjbaksa/jjbaksa/util/databinding/ImageDataBindingAdapter.kt b/app/src/main/java/com/jjbaksa/jjbaksa/util/databinding/ImageDataBindingAdapter.kt new file mode 100644 index 00000000..5222eaca --- /dev/null +++ b/app/src/main/java/com/jjbaksa/jjbaksa/util/databinding/ImageDataBindingAdapter.kt @@ -0,0 +1,22 @@ +package com.jjbaksa.jjbaksa.util.databinding + +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import coil.load +import coil.transform.CircleCropTransformation +import com.jjbaksa.jjbaksa.R + +@BindingAdapter("img") +fun ImageView.setImage(image: String?) { + if (image == null) { + return + } + + if (image.isEmpty()) { + load(R.drawable.baseline_supervised_user_circle_24) + } else { + load(image) { + transformations(CircleCropTransformation()) + } + } +} diff --git a/app/src/main/res/drawable/baseline_supervised_user_circle_24.xml b/app/src/main/res/drawable/baseline_supervised_user_circle_24.xml new file mode 100644 index 00000000..3ea888a6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_supervised_user_circle_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/bg_bottom_sheet_dialog.xml b/app/src/main/res/drawable/bg_bottom_sheet_dialog.xml new file mode 100644 index 00000000..010d9525 --- /dev/null +++ b/app/src/main/res/drawable/bg_bottom_sheet_dialog.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_withdrawal_button.xml b/app/src/main/res/drawable/bg_withdrawal_button.xml new file mode 100644 index 00000000..40e32955 --- /dev/null +++ b/app/src/main/res/drawable/bg_withdrawal_button.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circ_d9d9d9_stroke_ffffff.xml b/app/src/main/res/drawable/shape_circ_d9d9d9_stroke_ffffff.xml new file mode 100644 index 00000000..70851a1c --- /dev/null +++ b/app/src/main/res/drawable/shape_circ_d9d9d9_stroke_ffffff.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_withdrawal.xml b/app/src/main/res/layout/activity_withdrawal.xml new file mode 100644 index 00000000..d5c92551 --- /dev/null +++ b/app/src/main/res/layout/activity_withdrawal.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_mypage.xml b/app/src/main/res/layout/dialog_mypage.xml new file mode 100644 index 00000000..c810718a --- /dev/null +++ b/app/src/main/res/layout/dialog_mypage.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_navi_my_page.xml b/app/src/main/res/layout/fragment_navi_my_page.xml index aa8bbed6..de71fecb 100644 --- a/app/src/main/res/layout/fragment_navi_my_page.xml +++ b/app/src/main/res/layout/fragment_navi_my_page.xml @@ -8,6 +8,9 @@ + @@ -34,6 +38,7 @@ android:textColor="@color/color_000000" android:textSize="16dp" android:textStyle="bold" + android:text="@{vm.nickname}" app:layout_constraintStart_toEndOf="@id/profile_image_view" app:layout_constraintTop_toTopOf="@id/profile_image_view" tools:text="이병건이올씨다" /> @@ -42,9 +47,10 @@ android:id="@+id/follower_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="13dp" android:textColor="@color/color_000000" + android:layout_marginStart="2dp" android:textSize="14dp" + android:text="@{@string/my_page_followers(vm.profileFollowers)}" app:layout_constraintBottom_toBottomOf="@id/profile_name_text_view" app:layout_constraintStart_toEndOf="@id/profile_name_text_view" app:layout_constraintTop_toTopOf="@id/profile_name_text_view" @@ -56,10 +62,11 @@ android:layout_height="wrap_content" android:layout_marginTop="2dp" android:textColor="@color/color_000000" + android:text="@{@string/my_page_account(vm.account)}" android:textSize="14dp" app:layout_constraintStart_toStartOf="@id/profile_name_text_view" app:layout_constraintTop_toBottomOf="@id/profile_name_text_view" - tools:text="\@dangerousman" /> + tools:text="\@dangerousman"/> #CCFFFFFF #E6FFFFFF #fbfbfa + #d9d9d9 #595959 #666666 #989898 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1a66ec6..b276b955 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ 로그인 로그아웃 탈퇴하기 + 탈퇴하기 + 탈퇴 완료 아이디 비밀번호 자동로그인 @@ -31,6 +33,12 @@ 쩝쩝박사 이용약관(필수) 쩝쩝박사 서비스 이용약관은 bcsd lab에서 서비스를 제공함에 있어, 이용자간의 관리, 의무 및 책임 사항 등을 목적으로 합니다.\n본 약관(이하 ‘본 약관’이라 함)은 쩝쩝박사 어플리케이션과 모바일 및 PC 포함 이와 관련된 웹사이트들(이하 ‘쩝쩝박사’라 함)을 통하여 제공되는 모든 제품 및 서비스(이하 ‘본 서비스’라 함)와 관련하여 본 약관에 따라 당사와 이용계약을 체결하고 본 서비스를 이용하는 고객(이하 ‘회원’이라 함)과 당사 간의 권리, 의무 및 책임사항을 규정함을 목적으로 합니다. 당사에 회원가입을 하지 않고 단순 열람을 원하는 경우, 비회원 이용자를 위한 이용정책이 적용됩니다. 전체동의 + %s님,\n쩝쩝박사 학위를 포기하시겠어요...? + 계정을 삭제하시려는 이유가 궁금해요. + 쩝쩝박사에서 개선되면 좋을 점이나\n불편하셨던 점을 말씀해주세요! + 적극 반영하여 개선하도록 하겠습니다.\n쩝쩝박사의 문은 언제든 열려있으니 다시 찾아와 주세요! + 유의사항\n· 작성한 리뷰, 북마크, 프로필 등 모든 정보가 삭제 됩니다.\n· 추후 같은 계정으로 재가입해도 작성한 내역은 복구되지 않아요. + 자유롭게 작성해주세요 :) 새 비밀번호 비밀번호 확인 @@ -46,6 +54,7 @@ 새 비밀번호 확인 새 비밀번호를 입력하세요. 새 비밀번호를 설정해 주세요. + 새 비밀번호가 일치하지 않습니다. 인증번호 보내기 인증 완료 이메일로 발송된\n인증번호를 입력해 주세요. @@ -70,6 +79,7 @@ 아이디 중복확인을 해주세요. 회원가입을 축하합니다!\n당신을 어떻게 부르면 좋을까요? + 다음에 또 봐요! 재가입은 탈퇴 후 일주일 후 부터 가능합니다. 다음 완료 @@ -112,4 +122,15 @@ 다녀온 음식점의 리뷰를 작성해 보세요! 등록된 북마크가 없어요. 새로운 음식점을 저장해 보세요! + 가게 정보가 부족해요 + 사용이 불편해요 + 다른 앱을 더 많이 사용해요 + 새 계정을 만들고 싶어요 + 그 외 + + • 팔로우 %1$d + \@%s + %s/10 + %s/150 + %s님,\n프로필을 변경하시겠어요? \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 5efad671..ec9f1cca 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -14,6 +14,10 @@ true + + \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 14d0b4d4..39868f03 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -7,8 +7,8 @@ plugins { } android { - compileSdk = Constants.compileSdk + compileSdk = 32 defaultConfig { minSdk = Constants.minSdk targetSdk = Constants.targetSdk diff --git a/data/src/main/java/com/jjbaksa/data/api/AuthApi.kt b/data/src/main/java/com/jjbaksa/data/api/AuthApi.kt index cd9d5eb9..8bf40654 100644 --- a/data/src/main/java/com/jjbaksa/data/api/AuthApi.kt +++ b/data/src/main/java/com/jjbaksa/data/api/AuthApi.kt @@ -2,9 +2,33 @@ package com.jjbaksa.data.api import retrofit2.http.GET import com.jjbaksa.data.model.user.UserResp +import okhttp3.MultipartBody import retrofit2.Response +import com.jjbaksa.domain.resp.user.PasswordAndNicknameReq +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.Multipart +import retrofit2.http.PATCH +import retrofit2.http.POST +import retrofit2.http.Part interface AuthApi { @GET("user/me") suspend fun userMe(): Response + @PATCH("user/me") + suspend fun setUserNickname( + @Body item: PasswordAndNicknameReq + ): Response + @DELETE("user/me") + suspend fun deleteUser(): Response + @Multipart + @PATCH("user/profile") + suspend fun editUserProfileImage( + @Part profile: MultipartBody.Part + ): Response + @POST("user/withdraw-reason") + suspend fun saveWithdrawalReason( + @Body withdrawalReason: WithdrawalReasonReq + ): Response } diff --git a/data/src/main/java/com/jjbaksa/data/database/UserPreferences.kt b/data/src/main/java/com/jjbaksa/data/database/UserPreferences.kt index b6e7bde9..f044cba0 100644 --- a/data/src/main/java/com/jjbaksa/data/database/UserPreferences.kt +++ b/data/src/main/java/com/jjbaksa/data/database/UserPreferences.kt @@ -2,6 +2,7 @@ package com.jjbaksa.data.database import android.content.Context import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore @@ -12,6 +13,9 @@ object PreferenceKeys { val ACCESS_TOKEN = stringPreferencesKey("ACEESS_TOKEN") val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN") val ACCOUNT = stringPreferencesKey("ACCOUNT") + val NICKNAME = stringPreferencesKey("NICKNAME") + val FOLLOWERS = intPreferencesKey("FOLLOWERS") + val IMAGE = stringPreferencesKey("IMAGE") val PASSWORD = stringPreferencesKey("PASSWORD") var AUTO_LOGIN = booleanPreferencesKey("AUTO_LOGIN") val AUTH_PASSWORD_TOKEN = stringPreferencesKey("AUTH_PASSWORD_TOKEN") diff --git a/data/src/main/java/com/jjbaksa/data/datasource/UserDataSource.kt b/data/src/main/java/com/jjbaksa/data/datasource/UserDataSource.kt index 8ffe1e9a..48b71df3 100644 --- a/data/src/main/java/com/jjbaksa/data/datasource/UserDataSource.kt +++ b/data/src/main/java/com/jjbaksa/data/datasource/UserDataSource.kt @@ -8,6 +8,7 @@ import com.jjbaksa.domain.resp.user.LoginReq import com.jjbaksa.domain.resp.user.PasswordAndNicknameReq import com.jjbaksa.domain.resp.user.SignUpReq import com.jjbaksa.domain.resp.user.SignUpResp +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq import retrofit2.Response interface UserDataSource { @@ -19,15 +20,24 @@ interface UserDataSource { suspend fun getPasswordVerificationCode(id: String, email: String): Response suspend fun findAccount(email: String, code: String): Response suspend fun findPassword(findPasswordReq: FindPasswordReq): Response + suspend fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq): Response + suspend fun deleteUser(): Response suspend fun setNewPassword(token: String, item: PasswordAndNicknameReq): Response + suspend fun setNewNickname(item: PasswordAndNicknameReq): Response suspend fun saveAccessToken(accessToken: String) suspend fun saveAccount(account: String) + suspend fun saveNickname(nickname: String) + suspend fun saveFollowers(followers: Int) + suspend fun saveProfileImage(image: String) suspend fun savePassword(password: String) suspend fun saveRefreshToken(refreshToken: String) suspend fun saveAutoLogin(isAutoLogin: Boolean) suspend fun saveAuthPasswordToken(passwordToken: String) fun getAutoLoginFlag(): Boolean - fun getAcount(): String + fun getAccount(): String + fun getNickname(): String + fun getFollowers(): Int + fun getProfileImage(): String fun getPassword(): String fun getAccessToken(): String fun getAuthPasswordToken(): String diff --git a/data/src/main/java/com/jjbaksa/data/datasource/local/UserLocalDataSource.kt b/data/src/main/java/com/jjbaksa/data/datasource/local/UserLocalDataSource.kt index 1442737c..3e58e540 100644 --- a/data/src/main/java/com/jjbaksa/data/datasource/local/UserLocalDataSource.kt +++ b/data/src/main/java/com/jjbaksa/data/datasource/local/UserLocalDataSource.kt @@ -14,6 +14,7 @@ import com.jjbaksa.domain.resp.user.LoginReq import com.jjbaksa.domain.resp.user.PasswordAndNicknameReq import com.jjbaksa.domain.resp.user.SignUpReq import com.jjbaksa.domain.resp.user.SignUpResp +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -61,6 +62,18 @@ class UserLocalDataSource @Inject constructor( TODO("Not yet implemented") } + override suspend fun setNewNickname(item: PasswordAndNicknameReq): Response { + TODO("Not yet implemented") + } + + override suspend fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq): Response { + TODO("Not yet implemented") + } + + override suspend fun deleteUser(): Response { + TODO("Not yet implemented") + } + override suspend fun saveAccessToken(accessToken: String) { dataStore.edit { it[PreferenceKeys.ACCESS_TOKEN] = accessToken @@ -73,6 +86,24 @@ class UserLocalDataSource @Inject constructor( } } + override suspend fun saveNickname(nickname: String) { + dataStore.edit { + it[PreferenceKeys.NICKNAME] = nickname + } + } + + override suspend fun saveFollowers(followers: Int) { + dataStore.edit { + it[PreferenceKeys.FOLLOWERS] = followers + } + } + + override suspend fun saveProfileImage(image: String) { + dataStore.edit { + it[PreferenceKeys.IMAGE] = image + } + } + override suspend fun savePassword(password: String) { dataStore.edit { it[PreferenceKeys.PASSWORD] = password @@ -103,12 +134,30 @@ class UserLocalDataSource @Inject constructor( } } - override fun getAcount(): String { + override fun getAccount(): String { return runBlocking { dataStore.data.first()[PreferenceKeys.ACCOUNT] ?: "" } } + override fun getNickname(): String { + return runBlocking { + dataStore.data.first()[PreferenceKeys.NICKNAME] ?: "" + } + } + + override fun getFollowers(): Int { + return runBlocking { + dataStore.data.first()[PreferenceKeys.FOLLOWERS] ?: 0 + } + } + + override fun getProfileImage(): String { + return runBlocking { + dataStore.data.first()[PreferenceKeys.IMAGE] ?: "" + } + } + override fun getPassword(): String { return runBlocking { dataStore.data.first()[PreferenceKeys.PASSWORD] ?: "" diff --git a/data/src/main/java/com/jjbaksa/data/datasource/remote/UserRemoteDataSource.kt b/data/src/main/java/com/jjbaksa/data/datasource/remote/UserRemoteDataSource.kt index 89eaa5a3..2c79af58 100644 --- a/data/src/main/java/com/jjbaksa/data/datasource/remote/UserRemoteDataSource.kt +++ b/data/src/main/java/com/jjbaksa/data/datasource/remote/UserRemoteDataSource.kt @@ -11,6 +11,8 @@ import com.jjbaksa.domain.resp.user.LoginReq import com.jjbaksa.domain.resp.user.PasswordAndNicknameReq import com.jjbaksa.domain.resp.user.SignUpReq import com.jjbaksa.domain.resp.user.SignUpResp +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq +import okhttp3.MultipartBody import retrofit2.Response import javax.inject.Inject @@ -57,12 +59,33 @@ class UserRemoteDataSource @Inject constructor( return noAuthApi.setNewPassword(token, item) } + override suspend fun setNewNickname(item: PasswordAndNicknameReq): Response { + return authApi.setUserNickname(item) + } + + override suspend fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq): Response { + return authApi.saveWithdrawalReason(withdrawalReason) + } + + override suspend fun deleteUser(): Response { + return authApi.deleteUser() + } + override suspend fun saveAccessToken(accessToken: String) { } override suspend fun saveAccount(account: String) { } + override suspend fun saveNickname(nickname: String) { + } + + override suspend fun saveFollowers(followers: Int) { + } + + override suspend fun saveProfileImage(image: String) { + } + override suspend fun savePassword(password: String) { } @@ -78,12 +101,27 @@ class UserRemoteDataSource @Inject constructor( suspend fun me(): Response { return authApi.userMe() } + suspend fun editUserProfileImage(profile: MultipartBody.Part): Response { + return authApi.editUserProfileImage(profile) + } override fun getAutoLoginFlag(): Boolean { return false } - override fun getAcount(): String { + override fun getAccount(): String { + return "" + } + + override fun getNickname(): String { + return "" + } + + override fun getFollowers(): Int { + return 0 + } + + override fun getProfileImage(): String { return "" } diff --git a/data/src/main/java/com/jjbaksa/data/mapper/FormDataUtil.kt b/data/src/main/java/com/jjbaksa/data/mapper/FormDataUtil.kt new file mode 100644 index 00000000..51e32438 --- /dev/null +++ b/data/src/main/java/com/jjbaksa/data/mapper/FormDataUtil.kt @@ -0,0 +1,109 @@ +package com.jjbaksa.data.mapper + +import android.annotation.SuppressLint +import android.content.ContentResolver +import android.net.Uri +import android.provider.OpenableColumns +import android.util.Log +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okio.BufferedSink +import okio.source +import java.io.File +import java.net.URLConnection +import kotlin.math.abs + +object FormDataUtil { + val secUnit = 60L + val milliSec = 1000L + + fun getBody(key: String, value: Any): MultipartBody.Part { + return MultipartBody.Part.createFormData(key, value.toString()) + } + + fun getImageBody(key: String, uri: Uri): MultipartBody.Part { + try { + return getImageBody(key, File(uri.path)) + } catch (e: Exception) { + Log.e("jdm_Tag", e.message.toString()) + throw e + } + } + + fun getImageBody(key: String, file: File): MultipartBody.Part { + return MultipartBody.Part.createFormData( + name = key, + filename = file.name, + body = file.asRequestBody( + URLConnection.guessContentTypeFromName(file.name).toMediaType() + ) + ) + } + + fun getVideoBody(key: String, file: File): MultipartBody.Part { + return MultipartBody.Part.createFormData( + name = key, + filename = file.name, + body = file.asRequestBody("video/*".toMediaType()) + ) + } + + @SuppressLint("Range") + fun Uri.asMultipart( + key: String = "multipartFile", + contentResolver: ContentResolver + ): MultipartBody.Part? { + return contentResolver.query(this, null, null, null, null)?.let { + it.use { cursor -> + if (cursor.moveToNext()) { + val displayName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + val requestBody = object : RequestBody() { + override fun contentType(): MediaType? { + return contentResolver.getType(this@asMultipart)?.toMediaType() + } + + override fun writeTo(sink: BufferedSink) { + sink.writeAll( + contentResolver.openInputStream(this@asMultipart)?.source()!! + ) + } + } + MultipartBody.Part.createFormData(key, displayName, requestBody) + } else { + null + } + } + } + } + + fun secondToStringHHMMWithUnit(timeS: Long, hourText: String, minuteText: String): String { + val timeArr = stringArrForTimeHHMMSS(timeS, false) + val bNegative = timeS < 0 + val sb = StringBuilder() + if (bNegative) { + sb.append("-") + } + + if (timeArr[0] > 0) { + sb.append(String.format("%d$hourText ", timeArr[0])) + } + sb.append(String.format("%d$minuteText", timeArr[1])) + return sb.toString() + } + + fun stringArrForTimeHHMMSS(time: Long, isMillis: Boolean): List { + val absTimeMs = abs(time) + val totalSeconds = if (isMillis) { + absTimeMs / milliSec + } else { + absTimeMs + } + val seconds = totalSeconds % secUnit + val minutes = totalSeconds / secUnit % secUnit + val hours = totalSeconds / (secUnit * secUnit) + return listOf(hours, minutes, seconds) + } +} diff --git a/data/src/main/java/com/jjbaksa/data/model/user/UserCountResp.kt b/data/src/main/java/com/jjbaksa/data/model/user/UserCountResp.kt index 416a40c7..dd2979f2 100644 --- a/data/src/main/java/com/jjbaksa/data/model/user/UserCountResp.kt +++ b/data/src/main/java/com/jjbaksa/data/model/user/UserCountResp.kt @@ -1,7 +1,12 @@ package com.jjbaksa.data.model.user +import com.google.gson.annotations.SerializedName + data class UserCountResp( + @SerializedName("friendCount") var friendCount: Int, + @SerializedName("id") var id: Long, + @SerializedName("reviewCount") var reviewCount: Int ) diff --git a/data/src/main/java/com/jjbaksa/data/model/user/UserProfileResp.kt b/data/src/main/java/com/jjbaksa/data/model/user/UserProfileResp.kt new file mode 100644 index 00000000..586f27f3 --- /dev/null +++ b/data/src/main/java/com/jjbaksa/data/model/user/UserProfileResp.kt @@ -0,0 +1,14 @@ +package com.jjbaksa.data.model.user + +import com.google.gson.annotations.SerializedName + +data class UserProfileResp( + @SerializedName("id") + var id: Int, + @SerializedName("originalName") + var originalName: String, + @SerializedName("path") + var path: String, + @SerializedName("url") + var url: String +) diff --git a/data/src/main/java/com/jjbaksa/data/model/user/UserResp.kt b/data/src/main/java/com/jjbaksa/data/model/user/UserResp.kt index 28ef221e..6e35e35a 100644 --- a/data/src/main/java/com/jjbaksa/data/model/user/UserResp.kt +++ b/data/src/main/java/com/jjbaksa/data/model/user/UserResp.kt @@ -1,11 +1,22 @@ package com.jjbaksa.data.model.user +import com.google.gson.annotations.SerializedName + data class UserResp( + @SerializedName("account") var account: String, + @SerializedName("email") var email: String, + @SerializedName("id") var id: Long, + @SerializedName("nickname") var nickname: String, + @SerializedName("oauthType") var oauthType: String, - var userCountResp: UserCountResp, + @SerializedName("profileImage") + var profileImage: UserProfileResp, + @SerializedName("userCountResponse") + var userCountResponse: UserCountResp, + @SerializedName("userType") var userType: String ) diff --git a/data/src/main/java/com/jjbaksa/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/jjbaksa/data/repository/UserRepositoryImpl.kt index 82a7dbcc..d9248dfd 100644 --- a/data/src/main/java/com/jjbaksa/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/com/jjbaksa/data/repository/UserRepositoryImpl.kt @@ -1,9 +1,10 @@ package com.jjbaksa.data.repository -import android.util.Log +import android.net.Uri import com.jjbaksa.data.SUCCESS import com.jjbaksa.data.datasource.local.UserLocalDataSource import com.jjbaksa.data.datasource.remote.UserRemoteDataSource +import com.jjbaksa.data.mapper.FormDataUtil import com.jjbaksa.data.mapper.RespMapper import com.jjbaksa.domain.base.ErrorType import com.jjbaksa.domain.base.RespResult @@ -15,6 +16,7 @@ import com.jjbaksa.domain.resp.user.SignUpReq import com.jjbaksa.domain.resp.user.SignUpResp import com.jjbaksa.domain.resp.user.FindPasswordReq import com.jjbaksa.domain.resp.user.PasswordAndNicknameReq +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq import javax.inject.Inject class UserRepositoryImpl @Inject constructor( @@ -43,7 +45,6 @@ class UserRepositoryImpl @Inject constructor( isAutoLogin: Boolean, onResult: (LoginResult) -> Unit ) { - val response = userRemoteDataSource.postLogin(LoginReq(account, password)) if (response != null) { @@ -71,7 +72,7 @@ class UserRepositoryImpl @Inject constructor( override suspend fun checkAuthEmail(email: String): FormatResp { val result = userRemoteDataSource.checkAuthEmail(email) return if (result.isSuccessful) { - FormatResp(result.isSuccessful, null, result.code()) + FormatResp(result.isSuccessful, "", result.code()) } else { val errorBodyJson = result.errorBody()!!.string() val errorBody = RespMapper.errorMapper(errorBodyJson) @@ -79,18 +80,17 @@ class UserRepositoryImpl @Inject constructor( } } - override suspend fun checkPassword(password: String): RespResult { + override suspend fun checkPassword(password: String): FormatResp { val response = userRemoteDataSource.checkPassword( "Bearer " + userLocalDataSource.getAccessToken(), password ) - Log.d("로그", "response : $response") return if (response.isSuccessful && response.code() == 200) { - RespResult.Success(response.isSuccessful) + FormatResp(response.isSuccessful, "", response.code()) } else { val errorBodyJson = response.errorBody()!!.string() val errorBody = RespMapper.errorMapper(errorBodyJson) - RespResult.Error(ErrorType(errorBody.errorMessage, errorBody.code)) + FormatResp(response.isSuccessful, errorBody.errorMessage, errorBody.code) } } @@ -100,7 +100,7 @@ class UserRepositoryImpl @Inject constructor( ): FormatResp { val response = userRemoteDataSource.getPasswordVerificationCode(id, email) return if (response.isSuccessful && response.code() == 200) { - FormatResp(response.isSuccessful, null, response.code()) + FormatResp(response.isSuccessful, "", response.code()) } else { val errorBodyJson = response.errorBody()!!.string() val errorBody = RespMapper.errorMapper(errorBodyJson) @@ -123,7 +123,7 @@ class UserRepositoryImpl @Inject constructor( val response = userRemoteDataSource.findPassword(user) return if (response.isSuccessful && response.code() == 200) { userLocalDataSource.saveAuthPasswordToken(response.body().toString()) - FormatResp(response.isSuccessful, null, response.code()) + FormatResp(response.isSuccessful, "", response.code()) } else { val errorBodyJson = response.errorBody()!!.string() val errorBody = RespMapper.errorMapper(errorBodyJson) @@ -133,12 +133,26 @@ class UserRepositoryImpl @Inject constructor( override suspend fun setNewPassword(password: String): FormatResp { val item = PasswordAndNicknameReq(password, null) + val token = userLocalDataSource.getAuthPasswordToken().ifEmpty { userLocalDataSource.getAccessToken() } val response = userRemoteDataSource.setNewPassword( - "Bearer " + userLocalDataSource.getAuthPasswordToken(), + "Bearer $token", item ) return if (response.isSuccessful && response.code() == 200) { - FormatResp(response.isSuccessful, null, response.code()) + FormatResp(response.isSuccessful, "", response.code()) + } else { + val errorBodyJson = response.errorBody()!!.string() + val errorBody = RespMapper.errorMapper(errorBodyJson) + FormatResp(response.isSuccessful, errorBody.errorMessage, response.code()) + } + } + + override suspend fun setNewNickname(nickname: String): FormatResp { + val item = PasswordAndNicknameReq(null, nickname) + val response = userRemoteDataSource.setNewNickname(item) + return if (response.isSuccessful && response.code() == 200) { + userLocalDataSource.saveNickname(nickname) + FormatResp(response.isSuccessful, "", response.code()) } else { val errorBodyJson = response.errorBody()!!.string() val errorBody = RespMapper.errorMapper(errorBodyJson) @@ -148,8 +162,42 @@ class UserRepositoryImpl @Inject constructor( override suspend fun me(): RespResult { val response = userRemoteDataSource.me() + return if (response.isSuccessful) { + userLocalDataSource.saveNickname(response.body()?.nickname ?: "") + userLocalDataSource.saveFollowers(response.body()?.userCountResponse?.friendCount ?: 0) + userLocalDataSource.saveProfileImage(response.body()?.profileImage?.path ?: "") + RespResult.Success(response.isSuccessful) + } else { + val errorBodyJson = response.errorBody()!!.string() + val errorBody = RespMapper.errorMapper(errorBodyJson) + RespResult.Error(ErrorType(errorBody.errorMessage, errorBody.code)) + } + } + + override suspend fun editUserProfileImage(profile: String): RespResult { + val fileBody = FormDataUtil.getImageBody("multipartFile", Uri.parse(profile)) + val response = userRemoteDataSource.editUserProfileImage(fileBody) + if (response.isSuccessful) { + return RespResult.Success(true) + } else { + return RespResult.Success(false) + } + } + override suspend fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq): RespResult { + val response = userRemoteDataSource.saveWithdrawalReason(withdrawalReason) return if (response.isSuccessful) { + RespResult.Success(true) + } else { + val errorBodyJson = response.errorBody()!!.string() + val errorBody = RespMapper.errorMapper(errorBodyJson) + RespResult.Error(ErrorType(errorBody.errorMessage, errorBody.code)) + } + } + + override suspend fun deleteUser(): RespResult { + val response = userRemoteDataSource.deleteUser() + return if (response.isSuccessful && response.code() == 204) { RespResult.Success(response.isSuccessful) } else { val errorBodyJson = response.errorBody()!!.string() @@ -163,10 +211,22 @@ class UserRepositoryImpl @Inject constructor( } override fun getAccount(): String { - return userLocalDataSource.getAcount() + return userLocalDataSource.getAccount() + } + + override fun getNickname(): String { + return userLocalDataSource.getNickname() + } + + override fun getFollowers(): Int { + return userLocalDataSource.getFollowers() + } + + override fun getProfileImage(): String { + return userLocalDataSource.getProfileImage() } - override fun getPasswrod(): String { + override fun getPassword(): String { return userLocalDataSource.getPassword() } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index f06e158b..4acf5c81 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -5,8 +5,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = Versions.javaVersion + targetCompatibility = Versions.javaVersion } dependencies { diff --git a/domain/src/main/java/com/jjbaksa/domain/repository/UserRepository.kt b/domain/src/main/java/com/jjbaksa/domain/repository/UserRepository.kt index 580cc607..7af4734c 100644 --- a/domain/src/main/java/com/jjbaksa/domain/repository/UserRepository.kt +++ b/domain/src/main/java/com/jjbaksa/domain/repository/UserRepository.kt @@ -6,6 +6,7 @@ import com.jjbaksa.domain.resp.user.FormatResp import com.jjbaksa.domain.resp.user.LoginResult import com.jjbaksa.domain.resp.user.SignUpReq import com.jjbaksa.domain.resp.user.SignUpResp +import com.jjbaksa.domain.resp.user.WithdrawalReasonReq interface UserRepository { suspend fun postSignUp(signUpReq: SignUpReq): SignUpResp? @@ -18,14 +19,21 @@ interface UserRepository { ) suspend fun checkAuthEmail(email: String): FormatResp - suspend fun checkPassword(password: String): RespResult + suspend fun checkPassword(password: String): FormatResp suspend fun getPasswordVerificationCode(id: String, email: String): FormatResp suspend fun findAccount(email: String, code: String): FormatResp suspend fun findPassword(user: FindPasswordReq): FormatResp suspend fun setNewPassword(password: String): FormatResp + suspend fun setNewNickname(nickname: String): FormatResp suspend fun me(): RespResult + suspend fun editUserProfileImage(photo: String): RespResult + suspend fun saveWithdrawalReason(withdrawalReason: WithdrawalReasonReq): RespResult + suspend fun deleteUser(): RespResult fun getAutoLoginFlag(): Boolean fun getAccount(): String - fun getPasswrod(): String + fun getNickname(): String + fun getFollowers(): Int + fun getProfileImage(): String + fun getPassword(): String fun getAccessToken(): String } diff --git a/domain/src/main/java/com/jjbaksa/domain/resp/user/FormatResp.kt b/domain/src/main/java/com/jjbaksa/domain/resp/user/FormatResp.kt index c1c11a39..d2a5dedd 100644 --- a/domain/src/main/java/com/jjbaksa/domain/resp/user/FormatResp.kt +++ b/domain/src/main/java/com/jjbaksa/domain/resp/user/FormatResp.kt @@ -2,6 +2,6 @@ package com.jjbaksa.domain.resp.user data class FormatResp( val isSuccess: Boolean, - val msg: String?, + val msg: String, val code: Int ) diff --git a/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonReq.kt b/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonReq.kt new file mode 100644 index 00000000..178f8aab --- /dev/null +++ b/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonReq.kt @@ -0,0 +1,6 @@ +package com.jjbaksa.domain.resp.user + +data class WithdrawalReasonReq( + val reason: String, + val discomfort: String +) diff --git a/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonResp.kt b/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonResp.kt new file mode 100644 index 00000000..6473bd7e --- /dev/null +++ b/domain/src/main/java/com/jjbaksa/domain/resp/user/WithdrawalReasonResp.kt @@ -0,0 +1,6 @@ +package com.jjbaksa.domain.resp.user + +data class WithdrawalReasonResp( + val isSuccess: Boolean, + val message: String? +) diff --git a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryActivity.kt b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryActivity.kt index 73e9dd83..6084a3f3 100644 --- a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryActivity.kt +++ b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryActivity.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.Intent import android.graphics.Color import android.os.Build +import android.util.Log import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels @@ -11,6 +12,7 @@ import androidx.recyclerview.widget.GridLayoutManager import com.example.imageselector.R import com.example.imageselector.base.BaseActivity import com.example.imageselector.databinding.ActivityGalleryBinding +import com.example.imageselector.model.Image import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -20,11 +22,9 @@ class GalleryActivity : BaseActivity() { val viewModel: GalleryViewModel by viewModels() var maxNum = 10 private val galleryAdapter: GalleryAdapter by lazy { - GalleryAdapter(this, viewModel.getSelectedImageList(), viewModel.getUriArr(), maxNum) { - viewModel.selectImage(viewModel.getUriArr()[it]) - galleryAdapter.notifyDataSetChanged() - } + GalleryAdapter(this) } + private val requestPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> when (isGranted) { @@ -49,23 +49,22 @@ class GalleryActivity : BaseActivity() { } } - private fun sendImageData() { - val intent = Intent() - val list = ArrayList() - list.addAll(viewModel.getSelectedImageUri()) - intent.putStringArrayListExtra("images", list) - setResult(RESULT_OK, intent) - finish() - } - private fun getAllPhotos() { viewModel.getAllPhotos() + galleryAdapter.submitList(viewModel.getSelectedImages()) with(binding) { recyclerView.layoutManager = GridLayoutManager(this@GalleryActivity, 3) recyclerView.adapter = galleryAdapter } } + override fun initView() { + checkPermission() + binding.viewmodel = viewModel + binding.lifecycleOwner = this + getIntentData() + } + private fun checkPermission() { val permissionList = arrayOf( Manifest.permission.WRITE_EXTERNAL_STORAGE, @@ -78,14 +77,17 @@ class GalleryActivity : BaseActivity() { } } - override fun initView() { - checkPermission() - binding.viewmodel = viewModel - binding.lifecycleOwner = this - getIntentData() + private fun getIntentData() { + if (intent.hasExtra("limit")) { + maxNum = intent.getIntExtra("limit", 10) + } } override fun subscribe() { + observeData() + } + + private fun observeData() { viewModel.currentValue.observe(this) { if (it >= maxNum) { binding.textViewSelectedPictureCount.setTextColor(Color.parseColor("#c4c4c4")) @@ -96,19 +98,59 @@ class GalleryActivity : BaseActivity() { } override fun initEvent() { - with(binding) { - textViewSendSelectedImage.setOnClickListener { - sendImageData() - } - imageButtonPreviousArrow.setOnClickListener { - finish() + backToPreviousScreen() + selectImages() + sendToImage() + } + + private fun backToPreviousScreen() { + binding.imageButtonPreviousArrow.setOnClickListener { + finish() + } + } + + private fun selectImages() { + galleryAdapter.onClickImageListener = object : GalleryAdapter.OnClickImageListener { + override fun onImageClick(image: Image, position: Int) { + if (viewModel.getSelectedImageUri().size < maxNum) { + updateImages(position) + } else { + if (viewModel.getSelectedImageUri().contains(image.uri)) { + updateImages(position) + } + } } } } - fun getIntentData() { - if (intent.hasExtra("limit")) { - maxNum = intent.getIntExtra("limit", 10) + private fun updateImages(position: Int) { + viewModel.selectImage(viewModel.getUriArr()[position]) + val updatedImages = + viewModel.getSelectedImages().map { Image(it.uri, it.index, it.isSelected) } + galleryAdapter.submitList(updatedImages) + } + + private fun sendToImage() { + binding.textViewSendSelectedImage.setOnClickListener { + sendImageData() + } + } + + private fun sendImageData() { + val intent = Intent() + val list = ArrayList() + list.addAll(viewModel.getSelectedImageUri()) + if (list.isNullOrEmpty()) { + Toast.makeText(this, "이미지를 선택해주세요.", Toast.LENGTH_SHORT).show() + } else { + intent.putStringArrayListExtra("images", list) + setResult(RESULT_OK, intent) + finish() } } + + override fun onDestroy() { + viewModel.clearData() + super.onDestroy() + } } diff --git a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryAdapter.kt b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryAdapter.kt index b69da504..4dad8b9b 100644 --- a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryAdapter.kt +++ b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryAdapter.kt @@ -1,70 +1,64 @@ package com.example.imageselector.gallery import android.content.Context +import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.widget.LinearLayout +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.example.imageselector.model.Image import com.example.imageselector.databinding.ItemLayoutBinding class GalleryAdapter( - val context: Context, - private val imageList: ArrayList, - private val uriArr: ArrayList, - private val maxNum: Int, - val onClick: (Int) -> Unit, -) : RecyclerView.Adapter() { - private val selectedImages = ArrayList() + val context: Context +) : ListAdapter(diffUtil){ - class ViewHolder( - private val binding: ItemLayoutBinding, - ) : - RecyclerView.ViewHolder(binding.root) { - val galleryView = binding.galleryView - val item = binding.item + var onClickImageListener: OnClickImageListener? = null - fun bind(image: Image) { - binding.selectedImage = image - } + interface OnClickImageListener { + fun onImageClick(image: Image, position: Int) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ViewHolder(binding) + return ViewHolder(ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val display = context.resources.displayMetrics - holder.galleryView.layoutParams = - LinearLayout.LayoutParams(display.widthPixels / 3 - 6, display.widthPixels / 3 - 6) + holder.bind(currentList[position], position) + } - Glide.with(context) - .load(uriArr[position]) - .into(holder.galleryView) + inner class ViewHolder(private val binding: ItemLayoutBinding): RecyclerView.ViewHolder(binding.root) { + private val display = context.resources.displayMetrics + fun bind(image: Image, position: Int) { + binding.selectedImage = image - holder.itemView.setOnClickListener { - if (selectedImages.size < maxNum) { - if (!selectedImages.contains(position)) { - selectedImages.add(position) - onClick(position) - } else { - selectedImages.remove(position) - onClick(position) - } - } else { - if (selectedImages.contains(position)) { - selectedImages.remove(position) - onClick(position) - } + binding.galleryView.also { galleryImage -> + galleryImage.layoutParams = + LinearLayout.LayoutParams(display.widthPixels / 3 - 6, display.widthPixels / 3 - 6) + Glide.with(context) + .load(image.uri) + .into(galleryImage) + } + + binding.root.setOnClickListener { + onClickImageListener?.onImageClick(image, position) } - } - holder.bind(imageList[position]) + } } + companion object { + val diffUtil = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Image, newItem: Image): Boolean { + return oldItem === newItem + } - override fun getItemCount(): Int { - return imageList.size + override fun areContentsTheSame(oldItem: Image, newItem: Image): Boolean { + return oldItem == newItem + } + } } } + diff --git a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryViewModel.kt b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryViewModel.kt index b20d1258..b5c6f802 100644 --- a/image_selector/src/main/java/com/example/imageselector/gallery/GalleryViewModel.kt +++ b/image_selector/src/main/java/com/example/imageselector/gallery/GalleryViewModel.kt @@ -14,9 +14,8 @@ import javax.inject.Inject class GalleryViewModel @Inject constructor( private val repository: ImageRepository, ) : ViewModel() { - val currentValue: LiveData - get() = _currentValue private val _currentValue = MutableLiveData() + val currentValue: LiveData get() = _currentValue fun getAllPhotos() { repository.getAllPhotos() @@ -39,15 +38,17 @@ class GalleryViewModel @Inject constructor( return repository.getUriArr() } - fun getSelectedImageList(): ArrayList { - return repository.getSelectedImageList() + fun getSelectedImages(): ArrayList { + return repository.getSelectedImages() } -} -object DataBindingAdapterUtil { - @JvmStatic - @BindingAdapter("select") - fun select(view: View, b: Boolean) { - view.isSelected = b + fun clearData() { + with(repository) { + getUriArr().clear() + getSelectedImages().clear() + getSelectedImageUri().clear() + } } } + + diff --git a/image_selector/src/main/java/com/example/imageselector/repository/ImageRepository.kt b/image_selector/src/main/java/com/example/imageselector/repository/ImageRepository.kt index 7d43b789..852bf812 100644 --- a/image_selector/src/main/java/com/example/imageselector/repository/ImageRepository.kt +++ b/image_selector/src/main/java/com/example/imageselector/repository/ImageRepository.kt @@ -8,5 +8,5 @@ interface ImageRepository { fun refreshSelectList() fun getSelectedImageUri(): ArrayList fun getUriArr(): ArrayList - fun getSelectedImageList(): ArrayList + fun getSelectedImages(): ArrayList } diff --git a/image_selector/src/main/java/com/example/imageselector/repository/ImageRepositoryImpl.kt b/image_selector/src/main/java/com/example/imageselector/repository/ImageRepositoryImpl.kt index 165fc8b1..d0a653d6 100644 --- a/image_selector/src/main/java/com/example/imageselector/repository/ImageRepositoryImpl.kt +++ b/image_selector/src/main/java/com/example/imageselector/repository/ImageRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.example.imageselector.repository import android.content.ContentResolver import android.provider.MediaStore +import android.util.Log import com.example.imageselector.model.Image import javax.inject.Inject @@ -9,7 +10,7 @@ class ImageRepositoryImpl @Inject constructor( private val contentResolver: ContentResolver, ) : ImageRepository { - private val selectedImage = ArrayList() + private val selectedImages = ArrayList() private val selectedImageUri = ArrayList() private val uriArr = ArrayList() @@ -21,7 +22,6 @@ class ImageRepositoryImpl @Inject constructor( null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC" ) - val imageList = ArrayList() if (cursor != null) { while (cursor.moveToNext()) { val uri = @@ -31,8 +31,7 @@ class ImageRepositoryImpl @Inject constructor( cursor.close() } for (uri in uriArr) { - selectedImage.add(Image(uri, 0, false)) - imageList.add(Image(uri, 0, false)) + selectedImages.add(Image(uri, 0, false)) } } @@ -46,14 +45,14 @@ class ImageRepositoryImpl @Inject constructor( } override fun refreshSelectList() { - for (data in selectedImage) { + for (data in selectedImages) { data.index = 0 data.isSelected = false } for (i in 0 until selectedImageUri.size) { val path = selectedImageUri[i] - for (data in selectedImage) { + for (data in selectedImages) { if (data.uri.equals(path)) { data.index = i + 1 data.isSelected = true @@ -71,7 +70,7 @@ class ImageRepositoryImpl @Inject constructor( return uriArr } - override fun getSelectedImageList(): ArrayList { - return selectedImage + override fun getSelectedImages(): ArrayList { + return selectedImages } } diff --git a/image_selector/src/main/java/com/example/imageselector/utils/DataBindingAdapter.kt b/image_selector/src/main/java/com/example/imageselector/utils/DataBindingAdapter.kt new file mode 100644 index 00000000..f7cf0ff2 --- /dev/null +++ b/image_selector/src/main/java/com/example/imageselector/utils/DataBindingAdapter.kt @@ -0,0 +1,12 @@ +package com.example.imageselector.utils + +import android.view.View +import androidx.databinding.BindingAdapter + +object DataBindingAdapterUtil { + @JvmStatic + @BindingAdapter("select") + fun select(view: View, b: Boolean) { + view.isSelected = b + } +} diff --git a/image_selector/src/main/res/layout/item_layout.xml b/image_selector/src/main/res/layout/item_layout.xml index da7f62d3..d48de532 100644 --- a/image_selector/src/main/res/layout/item_layout.xml +++ b/image_selector/src/main/res/layout/item_layout.xml @@ -7,6 +7,7 @@ +