Add consolidated alert rule editing
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
package se.ajpanton.notificationsmaster.alerts
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
/** All editable rule fields apart from app assignment, local order, and identity. */
|
||||||
|
data class AlertRuleDefinition(
|
||||||
|
val sources: Set<AlertSource>,
|
||||||
|
val matcher: AlertTextMatcher = AlertTextMatcher(),
|
||||||
|
val outcome: AlertOutcome,
|
||||||
|
val profileId: String? = null,
|
||||||
|
val playToCompletion: Boolean = false,
|
||||||
|
val allowDuringDnd: Boolean = false,
|
||||||
|
val enabled: Boolean = true,
|
||||||
|
val name: String? = null,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(sources.isNotEmpty())
|
||||||
|
require((outcome == AlertOutcome.PLAY_PROFILE) == (profileId != null))
|
||||||
|
require(name?.isNotBlank() != false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ConsolidatedAlertRule(
|
||||||
|
val definition: AlertRuleDefinition,
|
||||||
|
val ruleIds: Set<String>,
|
||||||
|
val packageNames: Set<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
object AlertRuleEditor {
|
||||||
|
fun consolidate(rules: Iterable<AlertRule>): List<ConsolidatedAlertRule> =
|
||||||
|
rules.groupBy(AlertRule::groupingKey).values.map { group ->
|
||||||
|
ConsolidatedAlertRule(
|
||||||
|
definition = group.first().definition(),
|
||||||
|
ruleIds = group.mapTo(linkedSetOf()) { it.id },
|
||||||
|
packageNames = group.mapTo(sortedSetOf()) { it.packageName },
|
||||||
|
)
|
||||||
|
}.sortedWith(compareBy<ConsolidatedAlertRule> { it.definition.name ?: "" }.thenBy { it.packageNames.first() })
|
||||||
|
|
||||||
|
fun updateConsolidated(
|
||||||
|
configuration: AlertConfiguration,
|
||||||
|
ruleIds: Set<String>,
|
||||||
|
definition: AlertRuleDefinition,
|
||||||
|
): AlertConfiguration = configuration.copy(
|
||||||
|
rules = configuration.rules.map { rule ->
|
||||||
|
if (rule.id in ruleIds) rule.withDefinition(definition) else rule
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun updateForApp(
|
||||||
|
configuration: AlertConfiguration,
|
||||||
|
ruleId: String,
|
||||||
|
definition: AlertRuleDefinition,
|
||||||
|
): AlertConfiguration = updateConsolidated(configuration, setOf(ruleId), definition)
|
||||||
|
|
||||||
|
fun addForApps(
|
||||||
|
configuration: AlertConfiguration,
|
||||||
|
packageNames: Iterable<String>,
|
||||||
|
definition: AlertRuleDefinition,
|
||||||
|
newId: () -> String = { UUID.randomUUID().toString() },
|
||||||
|
): AlertConfiguration {
|
||||||
|
val added = packageNames.distinct().map { packageName ->
|
||||||
|
AlertRule(
|
||||||
|
id = newId(),
|
||||||
|
packageName = packageName,
|
||||||
|
order = configuration.rules.filter { it.packageName == packageName }.maxOfOrNull { it.order }?.plus(1) ?: 0,
|
||||||
|
sources = definition.sources,
|
||||||
|
matcher = definition.matcher,
|
||||||
|
outcome = definition.outcome,
|
||||||
|
profileId = definition.profileId,
|
||||||
|
playToCompletion = definition.playToCompletion,
|
||||||
|
allowDuringDnd = definition.allowDuringDnd,
|
||||||
|
enabled = definition.enabled,
|
||||||
|
name = definition.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return configuration.copy(rules = configuration.rules + added)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AlertRule.definition() = AlertRuleDefinition(
|
||||||
|
sources, matcher, outcome, profileId, playToCompletion, allowDuringDnd, enabled, name,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AlertRule.withDefinition(definition: AlertRuleDefinition) = copy(
|
||||||
|
sources = definition.sources,
|
||||||
|
matcher = definition.matcher,
|
||||||
|
outcome = definition.outcome,
|
||||||
|
profileId = definition.profileId,
|
||||||
|
playToCompletion = definition.playToCompletion,
|
||||||
|
allowDuringDnd = definition.allowDuringDnd,
|
||||||
|
enabled = definition.enabled,
|
||||||
|
name = definition.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package se.ajpanton.notificationsmaster.alerts
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AlertRuleEditorTest {
|
||||||
|
private val profile = AlertProfile("profile", "Profile", "content://sound")
|
||||||
|
private val definition = AlertRuleDefinition(setOf(AlertSource.NOTIFICATION_POST), outcome = AlertOutcome.PLAY_PROFILE, profileId = profile.id)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun consolidatesIdenticalRulesAcrossApps() {
|
||||||
|
val consolidated = AlertRuleEditor.consolidate(listOf(rule("one", "chat.app", 1), rule("two", "mail.app", 4)))
|
||||||
|
|
||||||
|
assertEquals(1, consolidated.size)
|
||||||
|
assertEquals(setOf("one", "two"), consolidated.single().ruleIds)
|
||||||
|
assertEquals(setOf("chat.app", "mail.app"), consolidated.single().packageNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun globalEditPreservesEveryAppsIdentityAndOrder() {
|
||||||
|
val original = configuration(rule("one", "chat.app", 1), rule("two", "mail.app", 4))
|
||||||
|
val edited = AlertRuleEditor.updateConsolidated(
|
||||||
|
original,
|
||||||
|
setOf("one", "two"),
|
||||||
|
definition.copy(name = "All messages", playToCompletion = true),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(listOf("one", "two"), edited.rules.map { it.id })
|
||||||
|
assertEquals(listOf("chat.app", "mail.app"), edited.rules.map { it.packageName })
|
||||||
|
assertEquals(listOf(1, 4), edited.rules.map { it.order })
|
||||||
|
assertEquals(listOf("All messages", "All messages"), edited.rules.map { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun appEditSplitsOnlyThatAppsRule() {
|
||||||
|
val original = configuration(rule("one", "chat.app", 1), rule("two", "mail.app", 4))
|
||||||
|
val edited = AlertRuleEditor.updateForApp(
|
||||||
|
original,
|
||||||
|
"one",
|
||||||
|
definition.copy(matcher = AlertTextMatcher(mode = AlertTextMode.CONTAINS, value = "urgent")),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(2, AlertRuleEditor.consolidate(edited.rules).size)
|
||||||
|
assertEquals(AlertTextMode.CONTAINS, edited.rules.first { it.id == "one" }.matcher.mode)
|
||||||
|
assertEquals(AlertTextMode.ANY, edited.rules.first { it.id == "two" }.matcher.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun newRuleAppendsAfterExistingRulesForEachApp() {
|
||||||
|
val original = configuration(rule("one", "chat.app", 2))
|
||||||
|
val ids = iterOf("chat-new", "mail-new")
|
||||||
|
val edited = AlertRuleEditor.addForApps(original, listOf("chat.app", "mail.app"), definition, ids::next)
|
||||||
|
|
||||||
|
assertEquals(listOf(2, 3, 0), edited.rules.map { it.order })
|
||||||
|
assertEquals(listOf("chat-new", "mail-new"), edited.rules.drop(1).map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rule(id: String, packageName: String, order: Int) = AlertRule(
|
||||||
|
id, packageName, order, definition.sources, definition.matcher, definition.outcome, definition.profileId,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun configuration(vararg rules: AlertRule) = AlertConfiguration(listOf(profile), rules = rules.toList())
|
||||||
|
|
||||||
|
private fun iterOf(vararg values: String) = values.iterator()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user