From 29b4c99f08a5faf5d4c871f4c6322559b7ffbd6d Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 10 Nov 2025 10:42:37 -0600 Subject: [PATCH] Add residence deletion functionality for iOS and Android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../ui/screens/ResidenceDetailScreen.kt | 58 +++++++++++++++++++ .../mycrib/viewmodel/ResidenceViewModel.kt | 19 ++++++ .../Residence/ResidenceDetailView.swift | 44 ++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt index 3a2787a..88f6955 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt @@ -54,6 +54,8 @@ fun ResidenceDetailScreen( var showManageUsersDialog by remember { mutableStateOf(false) } var showReportSnackbar by remember { mutableStateOf(false) } var reportMessage by remember { mutableStateOf("") } + var showDeleteConfirmation by remember { mutableStateOf(false) } + val deleteState by residenceViewModel.deleteResidenceState.collectAsState() LaunchedEffect(residenceId) { 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) { CompleteTaskDialog( taskId = selectedTask!!.id, @@ -176,6 +194,33 @@ fun ResidenceDetailScreen( ) } + if (showDeleteConfirmation && residenceState is ApiResult.Success) { + val residence = (residenceState as ApiResult.Success).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() } LaunchedEffect(showReportSnackbar) { @@ -231,6 +276,19 @@ fun ResidenceDetailScreen( }) { 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( diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/viewmodel/ResidenceViewModel.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/viewmodel/ResidenceViewModel.kt index a56c634..35a4f2d 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/viewmodel/ResidenceViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/viewmodel/ResidenceViewModel.kt @@ -49,6 +49,9 @@ class ResidenceViewModel : ViewModel() { private val _generateReportState = MutableStateFlow>(ApiResult.Idle) val generateReportState: StateFlow> = _generateReportState + private val _deleteResidenceState = MutableStateFlow>(ApiResult.Idle) + val deleteResidenceState: StateFlow> = _deleteResidenceState + fun loadResidences() { viewModelScope.launch { _residencesState.value = ApiResult.Loading @@ -208,4 +211,20 @@ class ResidenceViewModel : ViewModel() { fun resetGenerateReportState() { _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 + } } diff --git a/iosApp/iosApp/Residence/ResidenceDetailView.swift b/iosApp/iosApp/Residence/ResidenceDetailView.swift index 8b2a1e2..0e564d5 100644 --- a/iosApp/iosApp/Residence/ResidenceDetailView.swift +++ b/iosApp/iosApp/Residence/ResidenceDetailView.swift @@ -16,6 +16,9 @@ struct ResidenceDetailView: View { @State private var selectedTaskForComplete: TaskDetail? @State private var hasAppeared = 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 { ZStack { @@ -134,6 +137,16 @@ struct ResidenceDetailView: View { }) { 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) { @@ -189,6 +202,14 @@ struct ResidenceDetailView: View { } message: { 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 { 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 { + // 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 {