Add residence deletion functionality for iOS and Android
- Add delete residence button to iOS ResidenceDetailView (primary owners only) - Add delete confirmation alert for iOS with destructive action - Implement deleteResidence API call in iOS with navigation on success - Add delete residence button to Android ResidenceDetailScreen (primary owners only) - Add delete confirmation dialog for Android with error-colored button - Add deleteResidenceState to ResidenceViewModel with StateFlow - Implement deleteResidence() and resetDeleteResidenceState() in ViewModel - Add LaunchedEffect to handle delete success (navigates back) and errors - Display delete button with red/error styling on both platforms - Restrict delete functionality to primary owners only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,8 @@ fun ResidenceDetailScreen(
|
|||||||
var showManageUsersDialog by remember { mutableStateOf(false) }
|
var showManageUsersDialog by remember { mutableStateOf(false) }
|
||||||
var showReportSnackbar by remember { mutableStateOf(false) }
|
var showReportSnackbar by remember { mutableStateOf(false) }
|
||||||
var reportMessage by remember { mutableStateOf("") }
|
var reportMessage by remember { mutableStateOf("") }
|
||||||
|
var showDeleteConfirmation by remember { mutableStateOf(false) }
|
||||||
|
val deleteState by residenceViewModel.deleteResidenceState.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(residenceId) {
|
LaunchedEffect(residenceId) {
|
||||||
residenceViewModel.getResidence(residenceId) { result ->
|
residenceViewModel.getResidence(residenceId) { result ->
|
||||||
@@ -126,6 +128,22 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle delete residence state
|
||||||
|
LaunchedEffect(deleteState) {
|
||||||
|
when (deleteState) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
residenceViewModel.resetDeleteResidenceState()
|
||||||
|
onNavigateBack()
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
reportMessage = (deleteState as ApiResult.Error).message
|
||||||
|
showReportSnackbar = true
|
||||||
|
residenceViewModel.resetDeleteResidenceState()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showCompleteDialog && selectedTask != null) {
|
if (showCompleteDialog && selectedTask != null) {
|
||||||
CompleteTaskDialog(
|
CompleteTaskDialog(
|
||||||
taskId = selectedTask!!.id,
|
taskId = selectedTask!!.id,
|
||||||
@@ -176,6 +194,33 @@ fun ResidenceDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDeleteConfirmation && residenceState is ApiResult.Success) {
|
||||||
|
val residence = (residenceState as ApiResult.Success<Residence>).data
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteConfirmation = false },
|
||||||
|
title = { Text("Delete Residence") },
|
||||||
|
text = { Text("Are you sure you want to delete ${residence.name}? This action cannot be undone.") },
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
showDeleteConfirmation = false
|
||||||
|
residenceViewModel.deleteResidence(residenceId)
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Delete")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showDeleteConfirmation = false }) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(showReportSnackbar) {
|
LaunchedEffect(showReportSnackbar) {
|
||||||
@@ -231,6 +276,19 @@ fun ResidenceDetailScreen(
|
|||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Edit, contentDescription = "Edit Residence")
|
Icon(Icons.Default.Edit, contentDescription = "Edit Residence")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete button - only show for primary owners
|
||||||
|
if (residence.isPrimaryOwner) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
showDeleteConfirmation = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Delete,
|
||||||
|
contentDescription = "Delete Residence",
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ class ResidenceViewModel : ViewModel() {
|
|||||||
private val _generateReportState = MutableStateFlow<ApiResult<com.mycrib.shared.network.GenerateReportResponse>>(ApiResult.Idle)
|
private val _generateReportState = MutableStateFlow<ApiResult<com.mycrib.shared.network.GenerateReportResponse>>(ApiResult.Idle)
|
||||||
val generateReportState: StateFlow<ApiResult<com.mycrib.shared.network.GenerateReportResponse>> = _generateReportState
|
val generateReportState: StateFlow<ApiResult<com.mycrib.shared.network.GenerateReportResponse>> = _generateReportState
|
||||||
|
|
||||||
|
private val _deleteResidenceState = MutableStateFlow<ApiResult<Unit>>(ApiResult.Idle)
|
||||||
|
val deleteResidenceState: StateFlow<ApiResult<Unit>> = _deleteResidenceState
|
||||||
|
|
||||||
fun loadResidences() {
|
fun loadResidences() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_residencesState.value = ApiResult.Loading
|
_residencesState.value = ApiResult.Loading
|
||||||
@@ -208,4 +211,20 @@ class ResidenceViewModel : ViewModel() {
|
|||||||
fun resetGenerateReportState() {
|
fun resetGenerateReportState() {
|
||||||
_generateReportState.value = ApiResult.Idle
|
_generateReportState.value = ApiResult.Idle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteResidence(residenceId: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_deleteResidenceState.value = ApiResult.Loading
|
||||||
|
val token = TokenStorage.getToken()
|
||||||
|
if (token != null) {
|
||||||
|
_deleteResidenceState.value = residenceApi.deleteResidence(token, residenceId)
|
||||||
|
} else {
|
||||||
|
_deleteResidenceState.value = ApiResult.Error("Not authenticated", 401)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetDeleteResidenceState() {
|
||||||
|
_deleteResidenceState.value = ApiResult.Idle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ struct ResidenceDetailView: View {
|
|||||||
@State private var selectedTaskForComplete: TaskDetail?
|
@State private var selectedTaskForComplete: TaskDetail?
|
||||||
@State private var hasAppeared = false
|
@State private var hasAppeared = false
|
||||||
@State private var showReportAlert = false
|
@State private var showReportAlert = false
|
||||||
|
@State private var showDeleteConfirmation = false
|
||||||
|
@State private var isDeleting = false
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -134,6 +137,16 @@ struct ResidenceDetailView: View {
|
|||||||
}) {
|
}) {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete button - only show for primary owners
|
||||||
|
if let residence = viewModel.selectedResidence, residence.isPrimaryOwner {
|
||||||
|
Button(action: {
|
||||||
|
showDeleteConfirmation = true
|
||||||
|
}) {
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showAddTask) {
|
.sheet(isPresented: $showAddTask) {
|
||||||
@@ -189,6 +202,14 @@ struct ResidenceDetailView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(viewModel.reportMessage ?? "")
|
Text(viewModel.reportMessage ?? "")
|
||||||
}
|
}
|
||||||
|
.alert("Delete Residence", isPresented: $showDeleteConfirmation) {
|
||||||
|
Button("Cancel", role: .cancel) { }
|
||||||
|
Button("Delete", role: .destructive) {
|
||||||
|
deleteResidence()
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("Are you sure you want to delete this residence? This action cannot be undone.")
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadResidenceData()
|
loadResidenceData()
|
||||||
}
|
}
|
||||||
@@ -224,6 +245,29 @@ struct ResidenceDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deleteResidence() {
|
||||||
|
guard let token = TokenStorage.shared.getToken() else { return }
|
||||||
|
|
||||||
|
isDeleting = true
|
||||||
|
|
||||||
|
let residenceApi = ResidenceApi(client: ApiClient_iosKt.createHttpClient())
|
||||||
|
residenceApi.deleteResidence(token: token, id: residenceId) { result, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.isDeleting = false
|
||||||
|
|
||||||
|
if result is ApiResultSuccess<KotlinUnit> {
|
||||||
|
// Navigate back to residence list
|
||||||
|
self.dismiss()
|
||||||
|
} else if let errorResult = result as? ApiResultError {
|
||||||
|
// Show error message
|
||||||
|
self.viewModel.errorMessage = errorResult.message
|
||||||
|
} else if let error = error {
|
||||||
|
self.viewModel.errorMessage = error.localizedDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
|
|||||||
Reference in New Issue
Block a user