From ba09bed24ffc4c794bebd9b4214cccfc54083650 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Tue, 18 Aug 2026 01:54:22 +0000 Subject: [PATCH] Allow alert rules to be removed --- .../ajpanton/notificationsmaster/AlertRuleDialog.kt | 11 ++++++++++- .../ajpanton/notificationsmaster/AppRulesFragment.kt | 7 +++++-- .../se/ajpanton/notificationsmaster/RulesFragment.kt | 8 ++++++-- .../notificationsmaster/alerts/AlertRuleEditor.kt | 3 +++ .../notificationsmaster/alerts/AlertRuleEditorTest.kt | 7 +++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/AlertRuleDialog.kt b/app/src/main/java/se/ajpanton/notificationsmaster/AlertRuleDialog.kt index 8413a88..198497a 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/AlertRuleDialog.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/AlertRuleDialog.kt @@ -25,6 +25,7 @@ object AlertRuleDialog { existing: AlertRule?, apps: List? = null, onSave: (String?, AlertRuleDefinition) -> Unit, + onDelete: (() -> Unit)? = null, ) { val form = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL; setPadding(dp(context, 24), 0, dp(context, 24), 0) } val app = apps?.let { @@ -45,7 +46,8 @@ object AlertRuleDialog { val dnd = SwitchMaterial(context).apply { text = "Allow during DND when supported"; isChecked = existing?.allowDuringDnd == true } listOf(profile, post, update, pattern, regex, protected, dnd).forEach(form::addView) val dialog = MaterialAlertDialogBuilder(context).setTitle(if (existing == null) "Add rule" else "Edit rule") - .setView(form).setNegativeButton("Cancel", null).setPositiveButton("Save", null).show() + .setView(form).setNegativeButton("Cancel", null).setPositiveButton("Save", null) + .apply { if (onDelete != null) setNeutralButton("Delete", null) }.show() dialog.getButton(android.content.DialogInterface.BUTTON_POSITIVE).setOnClickListener { val sources = buildSet { if (post.isChecked) add(AlertSource.NOTIFICATION_POST) @@ -74,6 +76,13 @@ object AlertRuleDialog { onSave(app?.selectedItem?.let { it as InstalledApp }?.packageName, definition) dialog.dismiss() } + if (onDelete != null) dialog.getButton(android.content.DialogInterface.BUTTON_NEUTRAL).setOnClickListener { + MaterialAlertDialogBuilder(context).setTitle("Delete rule?") + .setMessage("This cannot be undone.") + .setNegativeButton("Cancel", null) + .setPositiveButton("Delete") { _, _ -> onDelete(); dialog.dismiss() } + .show() + } } private fun dp(context: Context, value: Int) = (value * context.resources.displayMetrics.density).toInt() diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/AppRulesFragment.kt b/app/src/main/java/se/ajpanton/notificationsmaster/AppRulesFragment.kt index f55aac6..2737f12 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/AppRulesFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/AppRulesFragment.kt @@ -57,10 +57,13 @@ class AppRulesFragment : Fragment(R.layout.fragment_app_rules) { android.widget.Toast.makeText(requireContext(), "Create a profile first", android.widget.Toast.LENGTH_LONG).show() return } - AlertRuleDialog.show(requireContext(), configuration, existing) { _, definition -> + AlertRuleDialog.show(requireContext(), configuration, existing, + onSave = { _, definition -> store.save(if (existing == null) AlertRuleEditor.addForApps(configuration, listOf(packageName), definition) else AlertRuleEditor.updateForApp(configuration, existing.id, definition)) refresh() - } + }, + onDelete = existing?.let { rule -> { store.save(AlertRuleEditor.remove(store.load(), setOf(rule.id))); refresh() } }, + ) } private fun saveOrder() { diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/RulesFragment.kt b/app/src/main/java/se/ajpanton/notificationsmaster/RulesFragment.kt index 754d4e5..e450cec 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/RulesFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/RulesFragment.kt @@ -42,7 +42,9 @@ class RulesFragment : Fragment(R.layout.fragment_rules) { return } val existing = ruleIds?.let { ids -> configuration.rules.first { it.id in ids } } - AlertRuleDialog.show(requireContext(), configuration, existing, if (existing == null) InstalledApps.all(requireContext()) else null) { packageName, definition -> + AlertRuleDialog.show( + requireContext(), configuration, existing, if (existing == null) InstalledApps.all(requireContext()) else null, + onSave = { packageName, definition -> val updated = if (ruleIds == null) { AlertRuleEditor.addForApps(configuration, listOfNotNull(packageName), definition) } else { @@ -50,7 +52,9 @@ class RulesFragment : Fragment(R.layout.fragment_rules) { } store.save(updated) refresh() - } + }, + onDelete = ruleIds?.let { ids -> { store.save(AlertRuleEditor.remove(store.load(), ids)); refresh() } }, + ) } private fun AlertSource.shortName() = if (this == AlertSource.NOTIFICATION_POST) "Appearing" else "Edits" diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditor.kt b/app/src/main/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditor.kt index a9e2ab7..9564fc1 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditor.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditor.kt @@ -88,6 +88,9 @@ object AlertRuleEditor { }) } + fun remove(configuration: AlertConfiguration, ruleIds: Set): AlertConfiguration = + configuration.copy(rules = configuration.rules.filterNot { it.id in ruleIds }) + private fun AlertRule.definition() = AlertRuleDefinition( sources, matcher, outcome, profileId, playToCompletion, allowDuringDnd, enabled, name, ) diff --git a/app/src/test/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditorTest.kt b/app/src/test/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditorTest.kt index bbca6b8..69cccd0 100644 --- a/app/src/test/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditorTest.kt +++ b/app/src/test/java/se/ajpanton/notificationsmaster/alerts/AlertRuleEditorTest.kt @@ -66,6 +66,13 @@ class AlertRuleEditorTest { assertEquals(0, edited.rules.first { it.id == "other" }.order) } + @Test + fun removesExactlyTheRequestedRules() { + val edited = AlertRuleEditor.remove(configuration(rule("one", "chat.app", 0), rule("two", "mail.app", 0)), setOf("one")) + + assertEquals(listOf("two"), edited.rules.map { it.id }) + } + private fun rule(id: String, packageName: String, order: Int) = AlertRule( id, packageName, order, definition.sources, definition.matcher, definition.outcome, definition.profileId, )