Expose alert rule outcomes
This commit is contained in:
@@ -46,9 +46,19 @@ object AlertRuleDialog {
|
|||||||
}
|
}
|
||||||
}.also(form::addView)
|
}.also(form::addView)
|
||||||
}
|
}
|
||||||
|
val profiles = configuration.profiles
|
||||||
val profile = Spinner(context).apply {
|
val profile = Spinner(context).apply {
|
||||||
adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, configuration.profiles.map { it.name })
|
adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, profiles.map { it.name })
|
||||||
existing?.let { rule -> setSelection(configuration.profiles.indexOfFirst { it.id == rule.profileId }.coerceAtLeast(0)) }
|
existing?.let { rule -> setSelection(profiles.indexOfFirst { it.id == rule.profileId }.coerceAtLeast(0)) }
|
||||||
|
}
|
||||||
|
val outcomes = listOf(
|
||||||
|
AlertOutcome.PLAY_PROFILE to "Play profile",
|
||||||
|
AlertOutcome.SILENCE_ORIGINAL to "Silence original alert",
|
||||||
|
AlertOutcome.PASS_THROUGH to "Keep original alert",
|
||||||
|
)
|
||||||
|
val outcome = Spinner(context).apply {
|
||||||
|
adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, outcomes.map { it.second })
|
||||||
|
setSelection(outcomes.indexOfFirst { it.first == existing?.outcome }.coerceAtLeast(0))
|
||||||
}
|
}
|
||||||
val post = CheckBox(context).apply { text = "Appearing"; isChecked = existing == null || AlertSource.NOTIFICATION_POST in existing.sources }
|
val post = CheckBox(context).apply { text = "Appearing"; isChecked = existing == null || AlertSource.NOTIFICATION_POST in existing.sources }
|
||||||
val update = CheckBox(context).apply { text = "Edits"; isChecked = existing == null || AlertSource.NOTIFICATION_UPDATE in existing.sources }
|
val update = CheckBox(context).apply { text = "Edits"; isChecked = existing == null || AlertSource.NOTIFICATION_UPDATE in existing.sources }
|
||||||
@@ -59,11 +69,22 @@ object AlertRuleDialog {
|
|||||||
text = "Allow during DND (requires system support)"
|
text = "Allow during DND (requires system support)"
|
||||||
isChecked = existing?.allowDuringDnd == true
|
isChecked = existing?.allowDuringDnd == true
|
||||||
}
|
}
|
||||||
listOf(profile, post, update, pattern, regex, protected, dnd).forEach(form::addView)
|
listOf(outcome, profile, post, update, pattern, regex, protected, dnd).forEach(form::addView)
|
||||||
form.addView(android.widget.TextView(context).apply {
|
form.addView(android.widget.TextView(context).apply {
|
||||||
text = "Android blocks direct custom alerts during Do Not Disturb. This setting is retained for optional system support, but does not bypass Do Not Disturb in the normal app."
|
text = "Android blocks direct custom alerts during Do Not Disturb. This setting is retained for optional system support, but does not bypass Do Not Disturb in the normal app."
|
||||||
textSize = 14f
|
textSize = 14f
|
||||||
})
|
})
|
||||||
|
fun updateOutcomeFields() {
|
||||||
|
val playsProfile = outcomes[outcome.selectedItemPosition].first == AlertOutcome.PLAY_PROFILE
|
||||||
|
profile.isEnabled = playsProfile
|
||||||
|
protected.isEnabled = playsProfile
|
||||||
|
dnd.isEnabled = playsProfile
|
||||||
|
}
|
||||||
|
outcome.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) = Unit
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) = updateOutcomeFields()
|
||||||
|
}
|
||||||
|
updateOutcomeFields()
|
||||||
val dialog = MaterialAlertDialogBuilder(context).setTitle(if (existing == null) "Add rule" else "Edit rule")
|
val dialog = MaterialAlertDialogBuilder(context).setTitle(if (existing == null) "Add rule" else "Edit rule")
|
||||||
.setView(form).setNegativeButton("Cancel", null).setPositiveButton("Save", null)
|
.setView(form).setNegativeButton("Cancel", null).setPositiveButton("Save", null)
|
||||||
.apply { if (onDelete != null) setNeutralButton("Delete", null) }.show()
|
.apply { if (onDelete != null) setNeutralButton("Delete", null) }.show()
|
||||||
@@ -85,13 +106,20 @@ object AlertRuleDialog {
|
|||||||
pattern.error = "Invalid regular expression"
|
pattern.error = "Invalid regular expression"
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
val selectedOutcome = outcomes[outcome.selectedItemPosition].first
|
||||||
|
val profileId = if (selectedOutcome == AlertOutcome.PLAY_PROFILE) {
|
||||||
|
profiles.getOrNull(profile.selectedItemPosition)?.id ?: run {
|
||||||
|
android.widget.Toast.makeText(context, "Create a profile first", android.widget.Toast.LENGTH_LONG).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
} else null
|
||||||
val definition = AlertRuleDefinition(
|
val definition = AlertRuleDefinition(
|
||||||
sources,
|
sources,
|
||||||
matcher,
|
matcher,
|
||||||
AlertOutcome.PLAY_PROFILE,
|
selectedOutcome,
|
||||||
configuration.profiles[profile.selectedItemPosition].id,
|
profileId,
|
||||||
protected.isChecked,
|
protected.isEnabled && protected.isChecked,
|
||||||
dnd.isChecked,
|
dnd.isEnabled && dnd.isChecked,
|
||||||
)
|
)
|
||||||
onSave(apps?.let { selectedApps.toList() }, definition)
|
onSave(apps?.let { selectedApps.toList() }, definition)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
|||||||
@@ -53,10 +53,6 @@ class AppRulesFragment : Fragment(R.layout.fragment_app_rules) {
|
|||||||
|
|
||||||
private fun edit(existing: AlertRule?) {
|
private fun edit(existing: AlertRule?) {
|
||||||
val configuration = store.load()
|
val configuration = store.load()
|
||||||
if (configuration.profiles.isEmpty()) {
|
|
||||||
android.widget.Toast.makeText(requireContext(), "Create a profile first", android.widget.Toast.LENGTH_LONG).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
AlertRuleDialog.show(requireContext(), configuration, existing,
|
AlertRuleDialog.show(requireContext(), configuration, existing,
|
||||||
onSave = { _, definition ->
|
onSave = { _, definition ->
|
||||||
store.save(if (existing == null) AlertRuleEditor.addForApps(configuration, listOf(packageName), definition) else AlertRuleEditor.updateForApp(configuration, existing.id, definition))
|
store.save(if (existing == null) AlertRuleEditor.addForApps(configuration, listOf(packageName), definition) else AlertRuleEditor.updateForApp(configuration, existing.id, definition))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.view.View
|
|||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import se.ajpanton.notificationsmaster.alerts.AlertConfigurationStore
|
import se.ajpanton.notificationsmaster.alerts.AlertConfigurationStore
|
||||||
|
import se.ajpanton.notificationsmaster.alerts.AlertOutcome
|
||||||
|
import se.ajpanton.notificationsmaster.alerts.AlertRuleDefinition
|
||||||
import se.ajpanton.notificationsmaster.alerts.AlertRuleEditor
|
import se.ajpanton.notificationsmaster.alerts.AlertRuleEditor
|
||||||
import se.ajpanton.notificationsmaster.alerts.AlertSource
|
import se.ajpanton.notificationsmaster.alerts.AlertSource
|
||||||
import se.ajpanton.notificationsmaster.databinding.FragmentRulesBinding
|
import se.ajpanton.notificationsmaster.databinding.FragmentRulesBinding
|
||||||
@@ -27,8 +29,8 @@ class RulesFragment : Fragment(R.layout.fragment_rules) {
|
|||||||
binding!!.rules.removeAllViews()
|
binding!!.rules.removeAllViews()
|
||||||
AlertRuleEditor.consolidate(configuration.rules).forEach { rule ->
|
AlertRuleEditor.consolidate(configuration.rules).forEach { rule ->
|
||||||
binding!!.rules.addView(Button(requireContext()).apply {
|
binding!!.rules.addView(Button(requireContext()).apply {
|
||||||
val profile = configuration.profiles.firstOrNull { it.id == rule.definition.profileId }?.name ?: "Missing profile"
|
text = rule.definition.outcomeLabel(configuration) + "\n" +
|
||||||
text = profile + "\n" + rule.packageNames.joinToString() + " • " + rule.definition.sources.joinToString { it.shortName() }
|
rule.packageNames.joinToString() + " • " + rule.definition.sources.joinToString { it.shortName() }
|
||||||
isAllCaps = false
|
isAllCaps = false
|
||||||
setOnClickListener { showEditor(rule.ruleIds) }
|
setOnClickListener { showEditor(rule.ruleIds) }
|
||||||
})
|
})
|
||||||
@@ -37,10 +39,6 @@ class RulesFragment : Fragment(R.layout.fragment_rules) {
|
|||||||
|
|
||||||
private fun showEditor(ruleIds: Set<String>? = null) {
|
private fun showEditor(ruleIds: Set<String>? = null) {
|
||||||
val configuration = store.load()
|
val configuration = store.load()
|
||||||
if (configuration.profiles.isEmpty()) {
|
|
||||||
android.widget.Toast.makeText(requireContext(), "Create a profile first", android.widget.Toast.LENGTH_LONG).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val existing = ruleIds?.let { ids -> configuration.rules.first { it.id in ids } }
|
val existing = ruleIds?.let { ids -> configuration.rules.first { it.id in ids } }
|
||||||
AlertRuleDialog.show(
|
AlertRuleDialog.show(
|
||||||
requireContext(), configuration, existing, if (existing == null) InstalledApps.all(requireContext()) else null,
|
requireContext(), configuration, existing, if (existing == null) InstalledApps.all(requireContext()) else null,
|
||||||
@@ -58,4 +56,10 @@ class RulesFragment : Fragment(R.layout.fragment_rules) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun AlertSource.shortName() = if (this == AlertSource.NOTIFICATION_POST) "Appearing" else "Edits"
|
private fun AlertSource.shortName() = if (this == AlertSource.NOTIFICATION_POST) "Appearing" else "Edits"
|
||||||
|
|
||||||
|
private fun AlertRuleDefinition.outcomeLabel(configuration: se.ajpanton.notificationsmaster.alerts.AlertConfiguration) = when (outcome) {
|
||||||
|
AlertOutcome.PLAY_PROFILE -> configuration.profiles.firstOrNull { it.id == profileId }?.name ?: "Missing profile"
|
||||||
|
AlertOutcome.SILENCE_ORIGINAL -> "Silence original alert"
|
||||||
|
AlertOutcome.PASS_THROUGH -> "Keep original alert"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -51,6 +51,16 @@ class AlertNotificationDispatcherTest {
|
|||||||
assertEquals(listOf("rule", "second"), AlertNotificationDispatcher.dispatch(configuration, event, false).map { it.ruleId })
|
assertEquals(listOf("rule", "second"), AlertNotificationDispatcher.dispatch(configuration, event, false).map { it.ruleId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun silenceAndPassThroughRulesDoNotQueueCustomPlayback() {
|
||||||
|
AlertOutcome.entries.filter { it != AlertOutcome.PLAY_PROFILE }.forEach { outcome ->
|
||||||
|
val rule = rule.copy(outcome = outcome, profileId = null)
|
||||||
|
val configuration = AlertConfiguration(rules = listOf(rule))
|
||||||
|
|
||||||
|
assertTrue(AlertNotificationDispatcher.dispatch(configuration, event, false).isEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun configuration(enabled: Boolean = true) = AlertConfiguration(
|
private fun configuration(enabled: Boolean = true) = AlertConfiguration(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
profiles = listOf(profile),
|
profiles = listOf(profile),
|
||||||
|
|||||||
Reference in New Issue
Block a user