Add delete account feature to mobile app
- DELETE /api/auth/account/ API call in AuthApi + APILayer - authProvider field on User model for email vs social auth detection - DeleteAccountDialog with password (email) or "type DELETE" (social) confirmation - Red "Delete Account" card on ProfileScreen - Navigation wired in App.kt (clears data, returns to login) - 10 i18n strings in strings.xml - ViewModel unit tests for delete account state
This commit is contained in:
@@ -556,6 +556,18 @@
|
|||||||
<string name="profile_app_name">honeyDue</string>
|
<string name="profile_app_name">honeyDue</string>
|
||||||
<string name="profile_edit_profile">Edit Profile</string>
|
<string name="profile_edit_profile">Edit Profile</string>
|
||||||
|
|
||||||
|
<!-- Delete Account -->
|
||||||
|
<string name="delete_account_title">Delete Account</string>
|
||||||
|
<string name="delete_account_subtitle">Permanently delete your account</string>
|
||||||
|
<string name="delete_account_warning">This action is permanent and cannot be undone. All your data will be deleted.</string>
|
||||||
|
<string name="delete_account_shared_warning">Any residences you own that are shared with other users will also be deleted.</string>
|
||||||
|
<string name="delete_account_confirm_password">Enter your password to confirm</string>
|
||||||
|
<string name="delete_account_confirm_type">Type DELETE to confirm</string>
|
||||||
|
<string name="delete_account_button">Delete My Account</string>
|
||||||
|
<string name="delete_account_cancel">Cancel</string>
|
||||||
|
<string name="delete_account_success">Account deleted successfully</string>
|
||||||
|
<string name="delete_account_failed">Failed to delete account</string>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
<string name="settings_notifications">Notification Preferences</string>
|
<string name="settings_notifications">Notification Preferences</string>
|
||||||
|
|||||||
@@ -665,6 +665,15 @@ fun App(
|
|||||||
popUpTo<ProfileRoute> { inclusive = true }
|
popUpTo<ProfileRoute> { inclusive = true }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAccountDeleted = {
|
||||||
|
// Clear token and lookups on account deletion
|
||||||
|
DataManager.clear()
|
||||||
|
isLoggedIn = false
|
||||||
|
isVerified = false
|
||||||
|
navController.navigate(LoginRoute) {
|
||||||
|
popUpTo<ProfileRoute> { inclusive = true }
|
||||||
|
}
|
||||||
|
},
|
||||||
onNavigateToNotificationPreferences = {
|
onNavigateToNotificationPreferences = {
|
||||||
navController.navigate(NotificationPreferencesRoute)
|
navController.navigate(NotificationPreferencesRoute)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ data class User(
|
|||||||
@SerialName("is_active") val isActive: Boolean = true,
|
@SerialName("is_active") val isActive: Boolean = true,
|
||||||
@SerialName("date_joined") val dateJoined: String,
|
@SerialName("date_joined") val dateJoined: String,
|
||||||
@SerialName("last_login") val lastLogin: String? = null,
|
@SerialName("last_login") val lastLogin: String? = null,
|
||||||
|
@SerialName("auth_provider") val authProvider: String? = null,
|
||||||
// Profile is included in CurrentUserResponse (/auth/me)
|
// Profile is included in CurrentUserResponse (/auth/me)
|
||||||
val profile: UserProfile? = null,
|
val profile: UserProfile? = null,
|
||||||
// Verified is returned directly in LoginResponse, and also in profile for CurrentUserResponse
|
// Verified is returned directly in LoginResponse, and also in profile for CurrentUserResponse
|
||||||
@@ -206,3 +207,12 @@ data class GoogleSignInResponse(
|
|||||||
val user: User,
|
val user: User,
|
||||||
@SerialName("is_new_user") val isNewUser: Boolean
|
@SerialName("is_new_user") val isNewUser: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete account request matching Go API
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class DeleteAccountRequest(
|
||||||
|
val password: String? = null,
|
||||||
|
val confirmation: String? = null
|
||||||
|
)
|
||||||
|
|||||||
@@ -1333,6 +1333,18 @@ object APILayer {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteAccount(password: String? = null, confirmation: String? = null): ApiResult<Unit> {
|
||||||
|
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||||
|
val result = authApi.deleteAccount(token, DeleteAccountRequest(password = password, confirmation = confirmation))
|
||||||
|
|
||||||
|
// Clear DataManager on successful deletion
|
||||||
|
if (result is ApiResult.Success) {
|
||||||
|
DataManager.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Notification Operations ====================
|
// ==================== Notification Operations ====================
|
||||||
|
|
||||||
suspend fun registerDevice(request: DeviceRegistrationRequest): ApiResult<DeviceRegistrationResponse> {
|
suspend fun registerDevice(request: DeviceRegistrationRequest): ApiResult<DeviceRegistrationResponse> {
|
||||||
|
|||||||
@@ -228,6 +228,26 @@ class AuthApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete Account
|
||||||
|
suspend fun deleteAccount(token: String, request: DeleteAccountRequest): ApiResult<Unit> {
|
||||||
|
return try {
|
||||||
|
val response = client.delete("$baseUrl/auth/account/") {
|
||||||
|
header("Authorization", "Token $token")
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
ApiResult.Success(Unit)
|
||||||
|
} else {
|
||||||
|
val errorMessage = ErrorParser.parseError(response)
|
||||||
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Google Sign In
|
// Google Sign In
|
||||||
suspend fun googleSignIn(request: GoogleSignInRequest): ApiResult<GoogleSignInResponse> {
|
suspend fun googleSignIn(request: GoogleSignInRequest): ApiResult<GoogleSignInResponse> {
|
||||||
return try {
|
return try {
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.tt.honeyDue.ui.components.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.tt.honeyDue.ui.theme.AppRadius
|
||||||
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog for confirming account deletion.
|
||||||
|
*
|
||||||
|
* For email auth users: shows a password field for confirmation.
|
||||||
|
* For social auth users: shows a "type DELETE to confirm" field.
|
||||||
|
*
|
||||||
|
* @param isSocialAuthUser Whether the user signed in via social auth (Apple/Google)
|
||||||
|
* @param onConfirm Callback with password (for email users) or confirmation text (for social users)
|
||||||
|
* @param onDismiss Callback when dialog is dismissed
|
||||||
|
* @param isLoading Whether the delete request is in progress
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DeleteAccountDialog(
|
||||||
|
isSocialAuthUser: Boolean,
|
||||||
|
onConfirm: (password: String?, confirmation: String?) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
isLoading: Boolean
|
||||||
|
) {
|
||||||
|
var inputValue by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val isConfirmEnabled = if (isSocialAuthUser) {
|
||||||
|
inputValue.equals("DELETE", ignoreCase = false)
|
||||||
|
} else {
|
||||||
|
inputValue.isNotBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(onDismissRequest = { if (!isLoading) onDismiss() }) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(AppRadius.lg),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.background
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(AppSpacing.lg)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(AppSpacing.xl),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
||||||
|
) {
|
||||||
|
// Warning Icon
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.delete_account_title),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Warning text
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.delete_account_warning),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shared residence warning
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.delete_account_shared_warning),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
||||||
|
|
||||||
|
// Input field
|
||||||
|
if (isSocialAuthUser) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = inputValue,
|
||||||
|
onValueChange = { inputValue = it },
|
||||||
|
label = { Text(stringResource(Res.string.delete_account_confirm_type)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
enabled = !isLoading,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = inputValue,
|
||||||
|
onValueChange = { inputValue = it },
|
||||||
|
label = { Text(stringResource(Res.string.delete_account_confirm_password)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
enabled = !isLoading,
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(AppSpacing.md)
|
||||||
|
) {
|
||||||
|
// Cancel button
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
enabled = !isLoading
|
||||||
|
) {
|
||||||
|
Text(stringResource(Res.string.delete_account_cancel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete button
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (isSocialAuthUser) {
|
||||||
|
onConfirm(null, inputValue)
|
||||||
|
} else {
|
||||||
|
onConfirm(inputValue, null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
enabled = isConfirmEnabled && !isLoading,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onError,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.delete_account_button),
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.tt.honeyDue.ui.components.HandleErrors
|
import com.tt.honeyDue.ui.components.HandleErrors
|
||||||
import com.tt.honeyDue.ui.components.common.ErrorCard
|
import com.tt.honeyDue.ui.components.common.ErrorCard
|
||||||
|
import com.tt.honeyDue.ui.components.dialogs.DeleteAccountDialog
|
||||||
import com.tt.honeyDue.ui.components.dialogs.ThemePickerDialog
|
import com.tt.honeyDue.ui.components.dialogs.ThemePickerDialog
|
||||||
import com.tt.honeyDue.utils.SubscriptionHelper
|
import com.tt.honeyDue.utils.SubscriptionHelper
|
||||||
import com.tt.honeyDue.ui.theme.AppRadius
|
import com.tt.honeyDue.ui.theme.AppRadius
|
||||||
@@ -40,6 +41,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||||||
fun ProfileScreen(
|
fun ProfileScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
|
onAccountDeleted: () -> Unit = {},
|
||||||
onNavigateToNotificationPreferences: () -> Unit = {},
|
onNavigateToNotificationPreferences: () -> Unit = {},
|
||||||
onNavigateToUpgrade: (() -> Unit)? = null,
|
onNavigateToUpgrade: (() -> Unit)? = null,
|
||||||
viewModel: AuthViewModel = viewModel { AuthViewModel() }
|
viewModel: AuthViewModel = viewModel { AuthViewModel() }
|
||||||
@@ -53,10 +55,13 @@ fun ProfileScreen(
|
|||||||
var isLoadingUser by remember { mutableStateOf(true) }
|
var isLoadingUser by remember { mutableStateOf(true) }
|
||||||
var showThemePicker by remember { mutableStateOf(false) }
|
var showThemePicker by remember { mutableStateOf(false) }
|
||||||
var showUpgradePrompt by remember { mutableStateOf(false) }
|
var showUpgradePrompt by remember { mutableStateOf(false) }
|
||||||
|
var showDeleteAccountDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val updateState by viewModel.updateProfileState.collectAsState()
|
val updateState by viewModel.updateProfileState.collectAsState()
|
||||||
|
val deleteAccountState by viewModel.deleteAccountState.collectAsState()
|
||||||
val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } }
|
val currentTheme by remember { derivedStateOf { ThemeManager.currentTheme } }
|
||||||
val currentSubscription by DataManager.subscription.collectAsState()
|
val currentSubscription by DataManager.subscription.collectAsState()
|
||||||
|
val currentUser by DataManager.currentUser.collectAsState()
|
||||||
|
|
||||||
// Handle errors for profile update
|
// Handle errors for profile update
|
||||||
updateState.HandleErrors(
|
updateState.HandleErrors(
|
||||||
@@ -91,6 +96,22 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle delete account state
|
||||||
|
LaunchedEffect(deleteAccountState) {
|
||||||
|
when (deleteAccountState) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
showDeleteAccountDialog = false
|
||||||
|
viewModel.resetDeleteAccountState()
|
||||||
|
onAccountDeleted()
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
// Error is shown in the dialog via isLoading becoming false
|
||||||
|
// The user can see the error and retry
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Re-enable profile update functionality
|
// TODO: Re-enable profile update functionality
|
||||||
/*
|
/*
|
||||||
LaunchedEffect(updateState) {
|
LaunchedEffect(updateState) {
|
||||||
@@ -631,6 +652,47 @@ fun ProfileScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
Spacer(modifier = Modifier.height(OrganicSpacing.lg))
|
||||||
|
|
||||||
|
// Delete Account Section
|
||||||
|
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.md))
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { showDeleteAccountDialog = true },
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(AppRadius.md)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(OrganicSpacing.lg),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.xs)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.delete_account_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.delete_account_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = stringResource(Res.string.delete_account_title),
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// App Version Section
|
// App Version Section
|
||||||
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.md))
|
OrganicDivider(modifier = Modifier.padding(vertical = OrganicSpacing.md))
|
||||||
Column(
|
Column(
|
||||||
@@ -673,6 +735,25 @@ fun ProfileScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete Account Dialog
|
||||||
|
if (showDeleteAccountDialog) {
|
||||||
|
val isSocialAuth = currentUser?.authProvider?.let {
|
||||||
|
it == "apple" || it == "google"
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
DeleteAccountDialog(
|
||||||
|
isSocialAuthUser = isSocialAuth,
|
||||||
|
onConfirm = { password, confirmation ->
|
||||||
|
viewModel.deleteAccount(password = password, confirmation = confirmation)
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showDeleteAccountDialog = false
|
||||||
|
viewModel.resetDeleteAccountState()
|
||||||
|
},
|
||||||
|
isLoading = deleteAccountState is ApiResult.Loading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrade Prompt Dialog
|
// Upgrade Prompt Dialog
|
||||||
if (showUpgradePrompt) {
|
if (showUpgradePrompt) {
|
||||||
UpgradePromptDialog(
|
UpgradePromptDialog(
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ class AuthViewModel : ViewModel() {
|
|||||||
private val _googleSignInState = MutableStateFlow<ApiResult<GoogleSignInResponse>>(ApiResult.Idle)
|
private val _googleSignInState = MutableStateFlow<ApiResult<GoogleSignInResponse>>(ApiResult.Idle)
|
||||||
val googleSignInState: StateFlow<ApiResult<GoogleSignInResponse>> = _googleSignInState
|
val googleSignInState: StateFlow<ApiResult<GoogleSignInResponse>> = _googleSignInState
|
||||||
|
|
||||||
|
private val _deleteAccountState = MutableStateFlow<ApiResult<Unit>>(ApiResult.Idle)
|
||||||
|
val deleteAccountState: StateFlow<ApiResult<Unit>> = _deleteAccountState
|
||||||
|
|
||||||
fun login(username: String, password: String) {
|
fun login(username: String, password: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_loginState.value = ApiResult.Loading
|
_loginState.value = ApiResult.Loading
|
||||||
@@ -271,4 +274,20 @@ class AuthViewModel : ViewModel() {
|
|||||||
APILayer.logout()
|
APILayer.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteAccount(password: String?, confirmation: String?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_deleteAccountState.value = ApiResult.Loading
|
||||||
|
val result = APILayer.deleteAccount(password = password, confirmation = confirmation)
|
||||||
|
_deleteAccountState.value = when (result) {
|
||||||
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||||
|
is ApiResult.Error -> result
|
||||||
|
else -> ApiResult.Error("Unknown error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetDeleteAccountState() {
|
||||||
|
_deleteAccountState.value = ApiResult.Idle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,4 +56,27 @@ class AuthViewModelTest {
|
|||||||
// Then
|
// Then
|
||||||
assertIs<ApiResult.Idle>(viewModel.registerState.value)
|
assertIs<ApiResult.Idle>(viewModel.registerState.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Delete Account Tests
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInitialDeleteAccountState() {
|
||||||
|
// Given
|
||||||
|
val viewModel = AuthViewModel()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertIs<ApiResult.Idle>(viewModel.deleteAccountState.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testResetDeleteAccountState() {
|
||||||
|
// Given
|
||||||
|
val viewModel = AuthViewModel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
viewModel.resetDeleteAccountState()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertIs<ApiResult.Idle>(viewModel.deleteAccountState.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user