Merge pull request #2001 from vector-im/feature/mention_display_name

Fix mention display name
This commit is contained in:
Benoit Marty 2020-08-26 17:35:23 +02:00 committed by GitHub
commit 95e80f0263
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 266 additions and 267 deletions

View File

@ -9,6 +9,7 @@ Improvements 🙌:
Bugfix 🐛: Bugfix 🐛:
- Display name not shown under Settings/General (#1926) - Display name not shown under Settings/General (#1926)
- Words containing my name should not trigger notifications (#1781)
- Fix changing language issue - Fix changing language issue
- Fix FontSize issue (#1483, #1787) - Fix FontSize issue (#1483, #1787)
- Fix bad color for settings icon on Android < 24 (#1786) - Fix bad color for settings icon on Android < 24 (#1786)

View File

@ -24,21 +24,24 @@ sealed class Action {
object DoNotNotify : Action() object DoNotNotify : Action()
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action() data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
data class Highlight(val highlight: Boolean) : Action() data class Highlight(val highlight: Boolean) : Action()
companion object {
const val ACTION_NOTIFY = "notify"
const val ACTION_DONT_NOTIFY = "dont_notify"
const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
const val ACTION_OBJECT_VALUE_KEY = "value"
const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
const val ACTION_OBJECT_VALUE_VALUE_RING = "ring"
}
} }
private const val ACTION_NOTIFY = "notify"
private const val ACTION_DONT_NOTIFY = "dont_notify"
private const val ACTION_COALESCE = "coalesce"
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
private const val ACTION_OBJECT_VALUE_KEY = "value"
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
/** /**
* Ref: https://matrix.org/docs/spec/client_server/latest#actions * Ref: https://matrix.org/docs/spec/client_server/latest#actions
* *
@ -69,18 +72,18 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
fun List<Action>.toJson(): List<Any> { fun List<Action>.toJson(): List<Any> {
return map { action -> return map { action ->
when (action) { when (action) {
is Action.Notify -> ACTION_NOTIFY is Action.Notify -> Action.ACTION_NOTIFY
is Action.DoNotNotify -> ACTION_DONT_NOTIFY is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY
is Action.Sound -> { is Action.Sound -> {
mapOf( mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND, Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
ACTION_OBJECT_VALUE_KEY to action.sound Action.ACTION_OBJECT_VALUE_KEY to action.sound
) )
} }
is Action.Highlight -> { is Action.Highlight -> {
mapOf( mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT, Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
ACTION_OBJECT_VALUE_KEY to action.highlight Action.ACTION_OBJECT_VALUE_KEY to action.highlight
) )
} }
} }
@ -92,26 +95,26 @@ fun PushRule.getActions(): List<Action> {
actions.forEach { actionStrOrObj -> actions.forEach { actionStrOrObj ->
when (actionStrOrObj) { when (actionStrOrObj) {
ACTION_NOTIFY -> Action.Notify Action.ACTION_NOTIFY -> Action.Notify
ACTION_DONT_NOTIFY -> Action.DoNotNotify Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify
is Map<*, *> -> { is Map<*, *> -> {
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) { when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) {
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> { Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue -> (actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
Action.Sound(stringValue) Action.Sound(stringValue)
} }
// When the value is not there, default sound (not specified by the spec) // When the value is not there, default sound (not specified by the spec)
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT) ?: Action.Sound(Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT)
} }
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> { Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue -> (actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
Action.Highlight(boolValue) Action.Highlight(boolValue)
} }
// When the value is not there, default is true, says the spec // When the value is not there, default is true, says the spec
?: Action.Highlight(true) ?: Action.Highlight(true)
} }
else -> { else -> {
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}") Timber.w("Unsupported set_tweak value ${actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]}")
null null
} }
} }

View File

@ -18,32 +18,8 @@ package org.matrix.android.sdk.api.pushrules
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
abstract class Condition(val kind: Kind) { interface Condition {
fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
enum class Kind(val value: String) { fun technicalDescription(): String
EventMatch("event_match"),
ContainsDisplayName("contains_display_name"),
RoomMemberCount("room_member_count"),
SenderNotificationPermission("sender_notification_permission"),
Unrecognised("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> EventMatch
"contains_display_name" -> ContainsDisplayName
"room_member_count" -> RoomMemberCount
"sender_notification_permission" -> SenderNotificationPermission
else -> Unrecognised
}
}
}
}
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
open fun technicalDescription(): String {
return "Kind: $kind"
}
} }

View File

@ -20,17 +20,15 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import timber.log.Timber import org.matrix.android.sdk.internal.util.caseInsensitiveFind
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) { class ContainsDisplayNameCondition : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveContainsDisplayNameCondition(event, this) return conditionResolver.resolveContainsDisplayNameCondition(event, this)
} }
override fun technicalDescription(): String { override fun technicalDescription() = "User is mentioned"
return "User is mentioned"
}
fun isSatisfied(event: Event, displayName: String): Boolean { fun isSatisfied(event: Event, displayName: String): Boolean {
val message = when (event.type) { val message = when (event.type) {
@ -45,31 +43,6 @@ class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
else -> null else -> null
} ?: return false } ?: return false
return caseInsensitiveFind(displayName, message.body) return message.body.caseInsensitiveFind(displayName)
}
companion object {
/**
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
*
* @param subString the string to search for
* @param longString the string to search in
* @return whether a match was found
*/
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
// add sanity checks
if (subString.isEmpty() || longString.isEmpty()) {
return false
}
try {
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
return regex.containsMatchIn(longString)
} catch (e: Exception) {
Timber.e(e, "## caseInsensitiveFind() : failed")
}
return false
}
} }
} }

View File

@ -18,6 +18,9 @@ package org.matrix.android.sdk.api.pushrules
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.caseInsensitiveFind
import org.matrix.android.sdk.internal.util.hasSpecialGlobChar
import org.matrix.android.sdk.internal.util.simpleGlobToRegExp
import timber.log.Timber import timber.log.Timber
class EventMatchCondition( class EventMatchCondition(
@ -29,16 +32,18 @@ class EventMatchCondition(
* The glob-style pattern to match against. Patterns with no special glob characters should * The glob-style pattern to match against. Patterns with no special glob characters should
* be treated as having asterisks prepended and appended when testing the condition. * be treated as having asterisks prepended and appended when testing the condition.
*/ */
val pattern: String val pattern: String,
) : Condition(Kind.EventMatch) { /**
* true to match only words. In this case pattern will not be considered as a glob
*/
val wordsOnly: Boolean
) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveEventMatchCondition(event, this) return conditionResolver.resolveEventMatchCondition(event, this)
} }
override fun technicalDescription(): String { override fun technicalDescription() = "'$key' matches '$pattern', words only '$wordsOnly'"
return "'$key' Matches '$pattern'"
}
fun isSatisfied(event: Event): Boolean { fun isSatisfied(event: Event): Boolean {
// TODO encrypted events? // TODO encrypted events?
@ -48,14 +53,18 @@ class EventMatchCondition(
// Patterns with no special glob characters should be treated as having asterisks prepended // Patterns with no special glob characters should be treated as having asterisks prepended
// and appended when testing the condition. // and appended when testing the condition.
try { return try {
val modPattern = if (hasSpecialGlobChar(pattern)) simpleGlobToRegExp(pattern) else simpleGlobToRegExp("*$pattern*") if (wordsOnly) {
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL) value.caseInsensitiveFind(pattern)
return regex.containsMatchIn(value) } else {
val modPattern = if (pattern.hasSpecialGlobChar()) pattern.simpleGlobToRegExp() else "*$pattern*".simpleGlobToRegExp()
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
regex.containsMatchIn(value)
}
} catch (e: Throwable) { } catch (e: Throwable) {
// e.g PatternSyntaxException // e.g PatternSyntaxException
Timber.e(e, "Failed to evaluate push condition") Timber.e(e, "Failed to evaluate push condition")
return false false
} }
} }
@ -78,27 +87,4 @@ class EventMatchCondition(
} }
return null return null
} }
companion object {
private fun hasSpecialGlobChar(glob: String): Boolean {
return glob.contains("*") || glob.contains("?")
}
// Very simple glob to regexp converter
private fun simpleGlobToRegExp(glob: String): String {
var out = "" // "^"
for (element in glob) {
when (element) {
'*' -> out += ".*"
'?' -> out += '.'.toString()
'.' -> out += "\\."
'\\' -> out += "\\\\"
else -> out += element
}
}
out += "" // '$'.toString()
return out
}
}
} }

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.pushrules
enum class Kind(val value: String) {
EventMatch("event_match"),
ContainsDisplayName("contains_display_name"),
RoomMemberCount("room_member_count"),
SenderNotificationPermission("sender_notification_permission"),
Unrecognised("");
companion object {
fun fromString(value: String): Kind {
return when (value) {
"event_match" -> EventMatch
"contains_display_name" -> ContainsDisplayName
"room_member_count" -> RoomMemberCount
"sender_notification_permission" -> SenderNotificationPermission
else -> Unrecognised
}
}
}
}

View File

@ -29,15 +29,13 @@ class RoomMemberCountCondition(
* If no prefix is present, this parameter defaults to ==. * If no prefix is present, this parameter defaults to ==.
*/ */
val iz: String val iz: String
) : Condition(Kind.RoomMemberCount) { ) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(event, this) return conditionResolver.resolveRoomMemberCountCondition(event, this)
} }
override fun technicalDescription(): String { override fun technicalDescription() = "Room member count is $iz"
return "Room member count is $iz"
}
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean { internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
// sanity checks // sanity checks

View File

@ -45,4 +45,6 @@ object RuleIds {
// Not documented // Not documented
const val RULE_ID_FALLBACK = ".m.rule.fallback" const val RULE_ID_FALLBACK = ".m.rule.fallback"
const val RULE_ID_REACTION = ".m.rule.reaction"
} }

View File

@ -28,15 +28,13 @@ class SenderNotificationPermissionCondition(
* type from the notifications object in the power level event content. * type from the notifications object in the power level event content.
*/ */
val key: String val key: String
) : Condition(Kind.SenderNotificationPermission) { ) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this) return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
} }
override fun technicalDescription(): String { override fun technicalDescription() = "User power level <$key>"
return "User power level <$key>"
}
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean { fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
val powerLevelsHelper = PowerLevelsHelper(powerLevels) val powerLevelsHelper = PowerLevelsHelper(powerLevels)

View File

@ -21,7 +21,9 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.pushrules.Condition import org.matrix.android.sdk.api.pushrules.Condition
import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
import org.matrix.android.sdk.api.pushrules.EventMatchCondition import org.matrix.android.sdk.api.pushrules.EventMatchCondition
import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
import timber.log.Timber import timber.log.Timber
@ -58,20 +60,20 @@ data class PushCondition(
val iz: String? = null val iz: String? = null
) { ) {
fun asExecutableCondition(): Condition? { fun asExecutableCondition(rule: PushRule): Condition? {
return when (Condition.Kind.fromString(kind)) { return when (Kind.fromString(kind)) {
Condition.Kind.EventMatch -> { Kind.EventMatch -> {
if (key != null && pattern != null) { if (key != null && pattern != null) {
EventMatchCondition(key, pattern) EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME)
} else { } else {
Timber.e("Malformed Event match condition") Timber.e("Malformed Event match condition")
null null
} }
} }
Condition.Kind.ContainsDisplayName -> { Kind.ContainsDisplayName -> {
ContainsDisplayNameCondition() ContainsDisplayNameCondition()
} }
Condition.Kind.RoomMemberCount -> { Kind.RoomMemberCount -> {
if (iz.isNullOrEmpty()) { if (iz.isNullOrEmpty()) {
Timber.e("Malformed ROOM_MEMBER_COUNT condition") Timber.e("Malformed ROOM_MEMBER_COUNT condition")
null null
@ -79,7 +81,7 @@ data class PushCondition(
RoomMemberCountCondition(iz) RoomMemberCountCondition(iz)
} }
} }
Condition.Kind.SenderNotificationPermission -> { Kind.SenderNotificationPermission -> {
if (key == null) { if (key == null) {
Timber.e("Malformed Sender Notification Permission condition") Timber.e("Malformed Sender Notification Permission condition")
null null
@ -87,7 +89,7 @@ data class PushCondition(
SenderNotificationPermissionCondition(key) SenderNotificationPermissionCondition(key)
} }
} }
Condition.Kind.Unrecognised -> { Kind.Unrecognised -> {
Timber.e("Unknown kind $kind") Timber.e("Unknown kind $kind")
null null
} }

View File

@ -63,7 +63,7 @@ data class PushRule(
* Add the default notification sound. * Add the default notification sound.
*/ */
fun setNotificationSound(): PushRule { fun setNotificationSound(): PushRule {
return setNotificationSound(ACTION_VALUE_DEFAULT) return setNotificationSound(Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT)
} }
fun getNotificationSound(): String? { fun getNotificationSound(): String? {
@ -109,13 +109,13 @@ data class PushRule(
fun setNotify(notify: Boolean): PushRule { fun setNotify(notify: Boolean): PushRule {
val mutableActions = actions.toMutableList() val mutableActions = actions.toMutableList()
mutableActions.remove(ACTION_DONT_NOTIFY) mutableActions.remove(Action.ACTION_DONT_NOTIFY)
mutableActions.remove(ACTION_NOTIFY) mutableActions.remove(Action.ACTION_NOTIFY)
if (notify) { if (notify) {
mutableActions.add(ACTION_NOTIFY) mutableActions.add(Action.ACTION_NOTIFY)
} else { } else {
mutableActions.add(ACTION_DONT_NOTIFY) mutableActions.add(Action.ACTION_DONT_NOTIFY)
} }
return copy(actions = mutableActions) return copy(actions = mutableActions)
@ -126,51 +126,12 @@ data class PushRule(
* *
* @return true if the rule should play sound * @return true if the rule should play sound
*/ */
fun shouldNotify() = actions.contains(ACTION_NOTIFY) fun shouldNotify() = actions.contains(Action.ACTION_NOTIFY)
/** /**
* Return true if the rule should not highlight the event. * Return true if the rule should not highlight the event.
* *
* @return true if the rule should not play sound * @return true if the rule should not play sound
*/ */
fun shouldNotNotify() = actions.contains(ACTION_DONT_NOTIFY) fun shouldNotNotify() = actions.contains(Action.ACTION_DONT_NOTIFY)
companion object {
/* ==========================================================================================
* Rule id
* ========================================================================================== */
const val RULE_ID_DISABLE_ALL = ".m.rule.master"
const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
const val RULE_ID_CONTAIN_DISPLAY_NAME = ".m.rule.contains_display_name"
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
const val RULE_ID_INVITE_ME = ".m.rule.invite_for_me"
const val RULE_ID_PEOPLE_JOIN_LEAVE = ".m.rule.member_event"
const val RULE_ID_CALL = ".m.rule.call"
const val RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS = ".m.rule.suppress_notices"
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
const val RULE_ID_AT_ROOMS = ".m.rule.roomnotif"
const val RULE_ID_TOMBSTONE = ".m.rule.tombstone"
const val RULE_ID_E2E_ONE_TO_ONE_ROOM = ".m.rule.encrypted_room_one_to_one"
const val RULE_ID_E2E_GROUP = ".m.rule.encrypted"
const val RULE_ID_REACTION = ".m.rule.reaction"
const val RULE_ID_FALLBACK = ".m.rule.fallback"
/* ==========================================================================================
* Actions
* ========================================================================================== */
const val ACTION_NOTIFY = "notify"
const val ACTION_DONT_NOTIFY = "dont_notify"
const val ACTION_COALESCE = "coalesce"
const val ACTION_SET_TWEAK_SOUND_VALUE = "sound"
const val ACTION_SET_TWEAK_HIGHLIGHT_VALUE = "highlight"
const val ACTION_PARAMETER_SET_TWEAK = "set_tweak"
const val ACTION_PARAMETER_VALUE = "value"
const val ACTION_VALUE_DEFAULT = "default"
const val ACTION_VALUE_RING = "ring"
}
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
/** /**
@ -51,7 +52,7 @@ data class RuleSet(
var result: PushRuleAndKind? = null var result: PushRuleAndKind? = null
// sanity check // sanity check
if (null != ruleId) { if (null != ruleId) {
if (PushRule.RULE_ID_CONTAIN_USER_NAME == ruleId) { if (RuleIds.RULE_ID_CONTAIN_USER_NAME == ruleId) {
result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) } result = findRule(content, ruleId)?.let { PushRuleAndKind(it, RuleSetKey.CONTENT) }
} else { } else {
// assume that the ruleId is unique. // assume that the ruleId is unique.

View File

@ -17,12 +17,12 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import com.squareup.moshi.Types import com.squareup.moshi.Types
import org.matrix.android.sdk.api.pushrules.Condition import io.realm.RealmList
import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.rest.PushCondition import org.matrix.android.sdk.api.pushrules.rest.PushCondition
import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.model.PushRuleEntity
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import io.realm.RealmList
import timber.log.Timber import timber.log.Timber
internal object PushRulesMapper { internal object PushRulesMapper {
@ -39,7 +39,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled, enabled = pushrule.enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.ruleId,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern) PushCondition(Kind.EventMatch.value, "content.body", pushrule.pattern)
) )
) )
} }
@ -60,7 +60,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled, enabled = pushrule.enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.ruleId,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId) PushCondition(Kind.EventMatch.value, "room_id", pushrule.ruleId)
) )
) )
} }
@ -72,7 +72,7 @@ internal object PushRulesMapper {
enabled = pushrule.enabled, enabled = pushrule.enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.ruleId,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId) PushCondition(Kind.EventMatch.value, "user_id", pushrule.ruleId)
) )
) )
} }

View File

@ -100,7 +100,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
return rules.firstOrNull { rule -> return rules.firstOrNull { rule ->
// All conditions must hold true for an event in order to apply the action for the event. // All conditions must hold true for an event in order to apply the action for the event.
rule.enabled && rule.conditions?.all { rule.enabled && rule.conditions?.all {
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
} ?: false } ?: false
} }
} }

View File

@ -18,7 +18,7 @@
package org.matrix.android.sdk.internal.session.room.notification package org.matrix.android.sdk.internal.session.room.notification
import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.Condition import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushCondition import org.matrix.android.sdk.api.pushrules.rest.PushCondition
@ -59,7 +59,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule?
} }
else -> { else -> {
val condition = PushCondition( val condition = PushCondition(
kind = Condition.Kind.EventMatch.value, kind = Kind.EventMatch.value,
key = "room_id", key = "room_id",
pattern = roomId pattern = roomId
) )

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.util
internal fun String.hasSpecialGlobChar(): Boolean {
return contains("*") || contains("?")
}
// Very simple glob to regexp converter
internal fun String.simpleGlobToRegExp(): String {
val string = this
return buildString {
// append("^")
string.forEach { char ->
when (char) {
'*' -> append(".*")
'?' -> append(".")
'.' -> append("\\.")
'\\' -> append("\\\\")
else -> append(char)
}
}
// append("$")
}
}

View File

@ -52,3 +52,25 @@ fun convertFromUTF8(s: String): String {
} }
fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this
/**
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
*
* @param subString the string to search for
* @return whether a match was found
*/
fun String.caseInsensitiveFind(subString: String): Boolean {
// add sanity checks
if (subString.isEmpty() || isEmpty()) {
return false
}
try {
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
return regex.containsMatchIn(this)
} catch (e: Exception) {
Timber.e(e, "## caseInsensitiveFind() : failed")
}
return false
}

View File

@ -16,6 +16,12 @@
package org.matrix.android.sdk.api.pushrules package org.matrix.android.sdk.api.pushrules
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.matrix.android.sdk.MatrixTest import org.matrix.android.sdk.MatrixTest
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
@ -24,28 +30,26 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.RoomGetter
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PushrulesConditionTest: MatrixTest { class PushRulesConditionTest : MatrixTest {
/* ========================================================================================== /* ==========================================================================================
* Test EventMatchCondition * Test EventMatchCondition
* ========================================================================================== */ * ========================================================================================== */
@Test private fun createSimpleTextEvent(text: String): Event {
fun test_eventmatch_type_condition() { return Event(
val condition = EventMatchCondition("type", "m.room.message")
val simpleTextEvent = Event(
type = "m.room.message", type = "m.room.message",
eventId = "mx0", eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(), content = MessageTextContent("m.text", text).toContent(),
originServerTs = 0) originServerTs = 0)
}
@Test
fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message", false)
val simpleTextEvent = createSimpleTextEvent("Yo wtf?")
val rm = RoomMemberContent( val rm = RoomMemberContent(
Membership.INVITE, Membership.INVITE,
@ -65,13 +69,9 @@ class PushrulesConditionTest: MatrixTest {
@Test @Test
fun test_eventmatch_path_condition() { fun test_eventmatch_path_condition() {
val condition = EventMatchCondition("content.msgtype", "m.text") val condition = EventMatchCondition("content.msgtype", "m.text", false)
val simpleTextEvent = Event( val simpleTextEvent = createSimpleTextEvent("Yo wtf?")
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
originServerTs = 0)
assert(condition.isSatisfied(simpleTextEvent)) assert(condition.isSatisfied(simpleTextEvent))
@ -86,49 +86,44 @@ class PushrulesConditionTest: MatrixTest {
).toContent(), ).toContent(),
originServerTs = 0 originServerTs = 0
).apply { ).apply {
assert(EventMatchCondition("content.membership", "invite").isSatisfied(this)) assert(EventMatchCondition("content.membership", "invite", false).isSatisfied(this))
} }
} }
@Test @Test
fun test_eventmatch_cake_condition() { fun test_eventmatch_cake_condition() {
val condition = EventMatchCondition("content.body", "cake") val condition = EventMatchCondition("content.body", "cake", false)
Event( assert(condition.isSatisfied(createSimpleTextEvent("How was the cake?")))
type = "m.room.message", assert(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?")))
eventId = "mx0",
content = MessageTextContent("m.text", "How was the cake?").toContent(),
originServerTs = 0
).apply {
assert(condition.isSatisfied(this))
}
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "Howwasthecake?").toContent(),
originServerTs = 0
).apply {
assert(condition.isSatisfied(this))
}
} }
@Test @Test
fun test_eventmatch_cakelie_condition() { fun test_eventmatch_cakelie_condition() {
val condition = EventMatchCondition("content.body", "cake*lie") val condition = EventMatchCondition("content.body", "cake*lie", false)
val simpleTextEvent = Event( assert(condition.isSatisfied(createSimpleTextEvent("How was the cakeisalie?")))
type = "m.room.message", }
eventId = "mx0",
content = MessageTextContent("m.text", "How was the cakeisalie?").toContent(),
originServerTs = 0)
assert(condition.isSatisfied(simpleTextEvent)) @Test
fun test_eventmatch_words_only_condition() {
val condition = EventMatchCondition("content.body", "ben", true)
assertFalse(condition.isSatisfied(createSimpleTextEvent("benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("Hello benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEvent("superben")))
assert(condition.isSatisfied(createSimpleTextEvent("ben")))
assert(condition.isSatisfied(createSimpleTextEvent("hello ben")))
assert(condition.isSatisfied(createSimpleTextEvent("ben is there")))
assert(condition.isSatisfied(createSimpleTextEvent("hello ben!")))
assert(condition.isSatisfied(createSimpleTextEvent("hello Ben!")))
assert(condition.isSatisfied(createSimpleTextEvent("BEN")))
} }
@Test @Test
fun test_notice_condition() { fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice") val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false)
Event( Event(
type = "m.room.message", type = "m.room.message",

View File

@ -25,9 +25,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
@ -40,6 +37,9 @@ import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.SimpleNotifiableEvent import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber import timber.log.Timber
/** /**
@ -196,7 +196,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
description = "", description = "",
type = null, type = null,
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
soundName = PushRule.ACTION_VALUE_DEFAULT, soundName = Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT,
isPushGatewayEvent = true isPushGatewayEvent = true
) )
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent) notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)

View File

@ -22,6 +22,8 @@ import android.view.View
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import im.vector.app.R import im.vector.app.R
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
@ -51,7 +53,7 @@ class PushRulePreference : VectorPreference {
get() { get() {
val safeRule = ruleAndKind?.pushRule ?: return NOTIFICATION_OFF_INDEX val safeRule = ruleAndKind?.pushRule ?: return NOTIFICATION_OFF_INDEX
if (safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) { if (safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
if (safeRule.shouldNotNotify()) { if (safeRule.shouldNotNotify()) {
return if (safeRule.enabled) { return if (safeRule.enabled) {
NOTIFICATION_OFF_INDEX NOTIFICATION_OFF_INDEX
@ -108,7 +110,7 @@ class PushRulePreference : VectorPreference {
val safeKind = ruleAndKind?.kind ?: return null val safeKind = ruleAndKind?.kind ?: return null
return if (index != ruleStatusIndex) { return if (index != ruleStatusIndex) {
if (safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) { if (safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
when (index) { when (index) {
NOTIFICATION_OFF_INDEX -> { NOTIFICATION_OFF_INDEX -> {
safeRule.copy(enabled = true) safeRule.copy(enabled = true)
@ -128,7 +130,7 @@ class PushRulePreference : VectorPreference {
} }
} else { } else {
if (NOTIFICATION_OFF_INDEX == index) { if (NOTIFICATION_OFF_INDEX == index) {
if (safeKind == RuleSetKey.UNDERRIDE || safeRule.ruleId == PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) { if (safeKind == RuleSetKey.UNDERRIDE || safeRule.ruleId == RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
safeRule.setNotify(false) safeRule.setNotify(false)
} else { } else {
safeRule.copy(enabled = false) safeRule.copy(enabled = false)
@ -137,15 +139,15 @@ class PushRulePreference : VectorPreference {
val newRule = safeRule.copy(enabled = true) val newRule = safeRule.copy(enabled = true)
.setNotify(true) .setNotify(true)
.setHighlight(safeKind != RuleSetKey.UNDERRIDE .setHighlight(safeKind != RuleSetKey.UNDERRIDE
&& safeRule.ruleId != PushRule.RULE_ID_INVITE_ME && safeRule.ruleId != RuleIds.RULE_ID_INVITE_ME
&& NOTIFICATION_NOISY_INDEX == index) && NOTIFICATION_NOISY_INDEX == index)
if (NOTIFICATION_NOISY_INDEX == index) { if (NOTIFICATION_NOISY_INDEX == index) {
newRule.setNotificationSound( newRule.setNotificationSound(
if (safeRule.ruleId == PushRule.RULE_ID_CALL) { if (safeRule.ruleId == RuleIds.RULE_ID_CALL) {
PushRule.ACTION_VALUE_RING Action.ACTION_OBJECT_VALUE_VALUE_RING
} else { } else {
PushRule.ACTION_VALUE_DEFAULT Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT
} }
) )
} else { } else {

View File

@ -16,13 +16,13 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import androidx.preference.Preference import androidx.preference.Preference
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.PushRulePreference import im.vector.app.core.preference.PushRulePreference
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor()
@ -92,17 +92,17 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor()
companion object { companion object {
// preference name <-> rule Id // preference name <-> rule Id
private val prefKeyToPushRuleId = mapOf( private val prefKeyToPushRuleId = mapOf(
"SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to PushRule.RULE_ID_CONTAIN_DISPLAY_NAME, "SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME,
"SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to PushRule.RULE_ID_CONTAIN_USER_NAME, "SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_USER_NAME,
"SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to PushRule.RULE_ID_ONE_TO_ONE_ROOM, "SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, "SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS,
"SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to PushRule.RULE_ID_INVITE_ME, "SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME,
"SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to PushRule.RULE_ID_CALL, "SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL,
"SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to PushRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS, "SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS,
"SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to PushRule.RULE_ID_AT_ROOMS, "SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_ROOM_NOTIF,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_E2E_ONE_TO_ONE_ROOM, "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM,
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to PushRule.RULE_ID_E2E_GROUP, "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED,
"SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to PushRule.RULE_ID_TOMBSTONE "SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE
) )
} }
} }

View File

@ -26,11 +26,11 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.EpoxyModelWithHolder
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.features.notifications.toNotificationAction import im.vector.app.features.notifications.toNotificationAction
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
@EpoxyModelClass(layout = R.layout.item_pushrule_raw) @EpoxyModelClass(layout = R.layout.item_pushrule_raw)
abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() { abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
@ -68,7 +68,7 @@ abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
val description = StringBuffer() val description = StringBuffer()
pushRule.conditions?.forEachIndexed { i, condition -> pushRule.conditions?.forEachIndexed { i, condition ->
if (i > 0) description.append("\n") if (i > 0) description.append("\n")
description.append(condition.asExecutableCondition()?.technicalDescription() description.append(condition.asExecutableCondition(pushRule)?.technicalDescription()
?: "UNSUPPORTED") ?: "UNSUPPORTED")
} }
if (description.isBlank()) { if (description.isBlank()) {