diff --git a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt index 4b7040de..6f4409c2 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt @@ -244,7 +244,7 @@ fun CronJobsScreen( ) } IconButton( - onClick = { viewModel.deleteCronJob(job.id) }, + onClick = { viewModel.requestDeleteJob(job) }, ) { Icon( imageVector = Icons.Filled.Delete, @@ -305,6 +305,25 @@ fun CronJobsScreen( }, ) } + + // ── Delete Confirm Dialog ── + state.deleteTarget?.let { job -> + AlertDialog( + onDismissRequest = { viewModel.dismissDeleteDialog() }, + title = { Text(stringResource(R.string.cron_delete_title)) }, + text = { Text(stringResource(R.string.cron_delete_message, job.name)) }, + confirmButton = { + TextButton(onClick = { viewModel.confirmDeleteJob() }) { + Text(stringResource(R.string.action_delete)) + } + }, + dismissButton = { + TextButton(onClick = { viewModel.dismissDeleteDialog() }) { + Text(stringResource(R.string.action_cancel)) + } + }, + ) + } } @Composable diff --git a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt index 39f04dab..c54e2313 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsViewModel.kt @@ -32,6 +32,8 @@ data class CronJobsUiState( val jobs: List = emptyList(), val errorMessage: String? = null, val toastMessage: String? = null, + // Delete-confirm state (non-null while the confirm dialog is up) + val deleteTarget: CronJob? = null, // Editor state val editorState: CronJobEditorState = CronJobEditorState(), ) @@ -196,6 +198,20 @@ class CronJobsViewModel : } } + fun requestDeleteJob(job: CronJob) { + _uiState.update { it.copy(deleteTarget = job) } + } + + fun dismissDeleteDialog() { + _uiState.update { it.copy(deleteTarget = null) } + } + + fun confirmDeleteJob() { + val target = _uiState.value.deleteTarget ?: return + _uiState.update { it.copy(deleteTarget = null) } + deleteCronJob(target.id) + } + // ── Editor ── fun openNewJobDialog() { diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fbf07d21..7b241fd4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -586,6 +586,8 @@ 저장 변경사항을 취소할까요? 저장되지 않은 변경사항이 있습니다. 취소하시겠습니까? + 크론 작업을 삭제할까요? + \"%1$s\" 작업을 삭제하시겠습니까? 되돌릴 수 없습니다. 아직 업적이 없습니다 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c5a3937..5ea0a5b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -828,6 +828,8 @@ Save Discard changes? You have unsaved changes. Discard them? + Delete job? + Delete \"%1$s\"? This can\'t be undone. No achievements yet diff --git a/app/src/test/java/com/m57/hermescontrol/ui/cron/CronJobsViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/cron/CronJobsViewModelTest.kt index 2eb42c2a..d93ce12d 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/cron/CronJobsViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/cron/CronJobsViewModelTest.kt @@ -226,6 +226,51 @@ class CronJobsViewModelTest { assertFalse(vm.uiState.value.editorState.isOpen) } + @Test + fun `requestDeleteJob sets the delete target without deleting`() { + val vm = createViewModel() + val job = CronJob(id = "j-del", name = "Doomed") + + vm.requestDeleteJob(job) + + assertEquals(job, vm.uiState.value.deleteTarget) + coVerify(exactly = 0) { mockApi.deleteCronJob(any()) } + } + + @Test + fun `dismissDeleteDialog clears the target without deleting`() { + val vm = createViewModel() + vm.requestDeleteJob(CronJob(id = "j-del", name = "Doomed")) + + vm.dismissDeleteDialog() + + assertNull(vm.uiState.value.deleteTarget) + coVerify(exactly = 0) { mockApi.deleteCronJob(any()) } + } + + @Test + fun `confirmDeleteJob clears the target and deletes the job`() { + val vm = createViewModel() + vm.requestDeleteJob(CronJob(id = "j-del", name = "Doomed")) + + vm.confirmDeleteJob() + settle() + + assertNull(vm.uiState.value.deleteTarget) + coVerify { mockApi.deleteCronJob("j-del") } + } + + @Test + fun `confirmDeleteJob without a target is a no-op`() { + val vm = createViewModel() + + vm.confirmDeleteJob() + settle() + + assertNull(vm.uiState.value.deleteTarget) + coVerify(exactly = 0) { mockApi.deleteCronJob(any()) } + } + @Test fun `setMonitorMode switching source clears the other`() { val vm = createViewModel()