Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원탈퇴 기능 추가 #239

Merged
merged 18 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.PriceGuard"
tools:targetApi="34">
<activity
android:name=".ui.home.mypage.DeleteAccountActivity"
android:exported="false" />
Taewan-P marked this conversation as resolved.
Show resolved Hide resolved

<meta-data
android:name="com.google.firebase.messaging.price_notification"
Expand Down Expand Up @@ -91,7 +94,6 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package app.priceguard.data.dto.deleteaccount

import kotlinx.serialization.Serializable

@Serializable
data class DeleteAccountRequest(
val email: String,
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package app.priceguard.data.dto.deleteaccount

import kotlinx.serialization.Serializable

@Serializable
data class DeleteAccountResponse(
val statusCode: Int,
val message: String
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.priceguard.data.network

import app.priceguard.data.dto.deleteaccount.DeleteAccountRequest
import app.priceguard.data.dto.deleteaccount.DeleteAccountResponse
import app.priceguard.data.dto.firebase.FirebaseTokenUpdateRequest
import app.priceguard.data.dto.firebase.FirebaseTokenUpdateResponse
import app.priceguard.data.dto.login.LoginRequest
Expand All @@ -24,6 +26,11 @@ interface UserAPI {
@Body request: SignupRequest
): Response<SignupResponse>

@POST("remove")
suspend fun deleteAccount(
@Body request: DeleteAccountRequest
): Response<DeleteAccountResponse>

@PUT("firebase/token")
suspend fun updateFirebaseToken(
@Header("Authorization") authToken: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface AuthRepository {
suspend fun signUp(email: String, userName: String, password: String): RepositoryResult<SignupResult, AuthErrorState>

suspend fun login(email: String, password: String): RepositoryResult<LoginResult, AuthErrorState>

suspend fun deleteAccount(email: String, password: String): RepositoryResult<Boolean, AuthErrorState>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.priceguard.data.repository.auth

import app.priceguard.data.dto.deleteaccount.DeleteAccountRequest
import app.priceguard.data.dto.login.LoginRequest
import app.priceguard.data.dto.signup.SignupRequest
import app.priceguard.data.network.UserAPI
Expand Down Expand Up @@ -73,4 +74,20 @@ class AuthRepositoryImpl @Inject constructor(private val userAPI: UserAPI) : Aut
}
}
}

override suspend fun deleteAccount(email: String, password: String): RepositoryResult<Boolean, AuthErrorState> {
val response = getApiResult {
userAPI.deleteAccount(DeleteAccountRequest(email, password))
}

return when (response) {
is APIResult.Success -> {
RepositoryResult.Success(true)
}

is APIResult.Error -> {
handleError(response.code)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}

override fun onDialogResult(result: Boolean) {
override fun onDialogResult(requestCode: Int, result: Boolean) {
if (result) {
productDetailViewModel.deleteProductTracking()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package app.priceguard.ui.home.mypage

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import app.priceguard.R
import app.priceguard.databinding.ActivityDeleteAccountBinding
import app.priceguard.ui.data.DialogConfirmAction
import app.priceguard.ui.login.LoginActivity
import app.priceguard.ui.util.lifecycle.repeatOnStarted
import app.priceguard.ui.util.showConfirmDialog
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class DeleteAccountActivity : AppCompatActivity() {

private lateinit var binding: ActivityDeleteAccountBinding
private val deleteAccountViewModel: DeleteAccountViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityDeleteAccountBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.viewModel = deleteAccountViewModel
binding.lifecycleOwner = this

deleteAccountViewModel.updateEmail(intent.getStringExtra("email") ?: "")

initView()
initCollector()
}

private fun initView() {
binding.btnDeleteAccountClose.setOnClickListener {
finish()
}
}

private fun initCollector() {
repeatOnStarted {
deleteAccountViewModel.state.collect { state ->
binding.btnDeleteAccount.isEnabled = state.isDeleteEnabled
}
}

repeatOnStarted {
deleteAccountViewModel.event.collect { event ->
when (event) {
DeleteAccountViewModel.DeleteAccountEvent.Logout -> {
Toast.makeText(
this@DeleteAccountActivity,
getString(R.string.success_delete_account),
Toast.LENGTH_SHORT
).show()
goBackToLoginActivity()
}
DeleteAccountViewModel.DeleteAccountEvent.WrongPassword -> {
showConfirmDialog(
getString(R.string.error_password),
getString(R.string.wrong_password),
DialogConfirmAction.NOTHING
)
}

DeleteAccountViewModel.DeleteAccountEvent.UndefinedError -> {
showConfirmDialog(
getString(R.string.error),
getString(R.string.undefined_error),
DialogConfirmAction.NOTHING
)
}
}
}
}
}

private fun goBackToLoginActivity() {
val intent = Intent(this, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package app.priceguard.ui.home.mypage

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.priceguard.data.repository.RepositoryResult
import app.priceguard.data.repository.auth.AuthErrorState
import app.priceguard.data.repository.auth.AuthRepository
import app.priceguard.data.repository.token.TokenRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

@HiltViewModel
class DeleteAccountViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val tokenRepository: TokenRepository
) : ViewModel() {

data class DeleteAccountState(
val email: String = "",
val password: String = "",
val isChecked: Boolean = false,
val isDeleteEnabled: Boolean = false
)

sealed class DeleteAccountEvent {
data object Logout : DeleteAccountEvent()
data object WrongPassword : DeleteAccountEvent()
data object UndefinedError : DeleteAccountEvent()
}

private val _state = MutableStateFlow(DeleteAccountState())
val state = _state.asStateFlow()

private val _event = MutableSharedFlow<DeleteAccountEvent>()
val event = _event.asSharedFlow()

fun deleteAccount() {
Taewan-P marked this conversation as resolved.
Show resolved Hide resolved
viewModelScope.launch {
val response = authRepository.deleteAccount(_state.value.email, _state.value.password)
when (response) {
is RepositoryResult.Error -> {
when (response.errorState) {
AuthErrorState.INVALID_REQUEST -> {
_event.emit(DeleteAccountEvent.WrongPassword)
}

else -> {
_event.emit(DeleteAccountEvent.UndefinedError)
}
}
}

is RepositoryResult.Success -> {
tokenRepository.clearTokens()
_event.emit(DeleteAccountEvent.Logout)
}
}
}
}

fun updateEmail(email: String) {
_state.value = _state.value.copy(email = email)
}

fun updatePassWord(password: String) {
_state.value = _state.value.copy(password = password)
updateDeleteEnabled()
}

fun updateChecked(isChecked: Boolean) {
_state.value = _state.value.copy(isChecked = isChecked)
updateDeleteEnabled()
}

private fun updateDeleteEnabled() {
_state.value = _state.value.copy(isDeleteEnabled = _state.value.password.isNotEmpty() && _state.value.isChecked)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
Expand All @@ -28,7 +29,7 @@

@Inject
lateinit var tokenRepository: TokenRepository
private var _binding: FragmentMyPageBinding? = null

Check failure on line 32 in android/app/src/main/java/app/priceguard/ui/home/mypage/MyPageFragment.kt

View workflow job for this annotation

GitHub Actions / Check Kotlin Format

[ktlint] reported by reviewdog 🐶 Backing property name is only allowed when a matching public property or function exists Raw Output: android/app/src/main/java/app/priceguard/ui/home/mypage/MyPageFragment.kt:32:17: error: Backing property name is only allowed when a matching public property or function exists (standard:property-naming)
private val binding get() = _binding!!
private val myPageViewModel: MyPageViewModel by viewModels()

Expand Down Expand Up @@ -78,7 +79,17 @@
}

Setting.LOGOUT -> {
showConfirmationDialogForResult()
showConfirmationDialogForResult(
R.string.logout_confirm_title,
R.string.logout_confirm_message,
Setting.LOGOUT.ordinal
)
}

Setting.DELETE_ACCOUNT -> {
val intent = Intent(requireActivity(), DeleteAccountActivity::class.java)
intent.putExtra("email", myPageViewModel.state.value.email)
startActivity(intent)
}
}
}
Expand Down Expand Up @@ -107,19 +118,29 @@
Setting.LOGOUT,
ContextCompat.getDrawable(requireActivity(), R.drawable.ic_logout),
getString(R.string.logout)
),
SettingItemInfo(
Setting.DELETE_ACCOUNT,
ContextCompat.getDrawable(requireActivity(), R.drawable.ic_close_red),
getString(R.string.delete_account)
)
)
}

private fun showConfirmationDialogForResult() {
private fun showConfirmationDialogForResult(
@StringRes title: Int,
@StringRes message: Int,
requestCode: Int
) {
val tag = "confirm_dialog_fragment_from_activity"
if (requireActivity().supportFragmentManager.findFragmentByTag(tag) != null) return

val dialogFragment = ConfirmDialogFragment()
val bundle = Bundle()
bundle.putString("title", getString(R.string.logout_confirm_title))
bundle.putString("message", getString(R.string.logout_confirm_message))
bundle.putString("title", getString(title))
bundle.putString("message", getString(message))
bundle.putString("actionString", DialogConfirmAction.CUSTOM.name)
bundle.putInt("requestCode", requestCode)
dialogFragment.arguments = bundle
dialogFragment.setOnDialogResultListener(this)
dialogFragment.show(requireActivity().supportFragmentManager, tag)
Expand All @@ -131,9 +152,9 @@
requireActivity().finish()
}

override fun onDialogResult(result: Boolean) {
if (result) {
myPageViewModel.logout()
override fun onDialogResult(requestCode: Int, result: Boolean) {
when (requestCode) {
Setting.LOGOUT.ordinal -> myPageViewModel.logout()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.priceguard.ui.home.mypage

import android.R
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
Expand Down Expand Up @@ -32,6 +34,12 @@ class MyPageSettingAdapter(
with(binding) {
settingItemInfo = item
listener = clickListener

if (item.id == Setting.DELETE_ACCOUNT) {
val typedValue = TypedValue()
binding.root.context.theme.resolveAttribute(R.attr.colorError, typedValue, true)
Taewan-P marked this conversation as resolved.
Show resolved Hide resolved
tvMyPageItemTitle.setTextColor(typedValue.data)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch

@HiltViewModel
class MyPageViewModel @Inject constructor(
val tokenRepository: TokenRepository
private val tokenRepository: TokenRepository
) : ViewModel() {

sealed class MyPageEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ enum class Setting {
NOTIFICATION,
THEME,
LICENSE,
LOGOUT
LOGOUT,
DELETE_ACCOUNT
}
Loading