Skip to content

Commit 5c743b0

Browse files
authored
feat: Allow emoji-matching via \p{Emoji} (#82)
2 parents 64f3f47 + 8447d0b commit 5c743b0

File tree

9 files changed

+63
-38
lines changed

9 files changed

+63
-38
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ android {
1919
applicationId = "co.adityarajput.notifilter"
2020
minSdk = 29
2121
targetSdk = 36
22-
versionCode = 27
23-
versionName = "4.8.1"
22+
versionCode = 28
23+
versionName = "4.9.0"
2424

2525
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2626
}
@@ -88,6 +88,7 @@ dependencies {
8888
implementation(libs.androidx.glance.appwidget)
8989
implementation(libs.androidx.glance.material3)
9090
implementation(libs.aboutlibraries.compose)
91+
implementation(libs.jemoji)
9192
testImplementation(libs.junit)
9293
androidTestImplementation(libs.androidx.junit)
9394
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/java/co/adityarajput/notifilter/data/models/Filter.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
44
import androidx.room.Embedded
55
import androidx.room.Entity
66
import androidx.room.PrimaryKey
7+
import co.adityarajput.notifilter.utils.containsMatchIn
78
import kotlinx.serialization.Serializable
89

910
@Serializable
@@ -38,18 +39,18 @@ data class Filter(
3839
fun matchesTextOf(notification: Notification): Boolean {
3940
return when (regexTarget) {
4041
RegexTarget.TITLE ->
41-
Regex(regexPattern).containsMatchIn(notification.title)
42+
regexPattern.containsMatchIn(notification.title)
4243

4344
RegexTarget.CONTENT ->
44-
Regex(regexPattern).containsMatchIn(notification.content)
45+
regexPattern.containsMatchIn(notification.content)
4546

4647
RegexTarget.OR ->
47-
Regex(regexPattern).containsMatchIn(notification.title) ||
48-
Regex(regexPattern).containsMatchIn(notification.content)
48+
regexPattern.containsMatchIn(notification.title) ||
49+
regexPattern.containsMatchIn(notification.content)
4950

5051
RegexTarget.AND ->
51-
Regex(regexPattern).containsMatchIn(notification.title) &&
52-
Regex(secondaryRegexPattern!!).containsMatchIn(notification.content)
52+
regexPattern.containsMatchIn(notification.title) &&
53+
secondaryRegexPattern!!.containsMatchIn(notification.content)
5354
}
5455
}
5556
}

app/src/main/java/co/adityarajput/notifilter/services/NotificationListener.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import co.adityarajput.notifilter.data.AppContainer
1919
import co.adityarajput.notifilter.data.Cache
2020
import co.adityarajput.notifilter.data.models.*
2121
import co.adityarajput.notifilter.utils.Logger
22+
import co.adityarajput.notifilter.utils.containsMatchIn
2223
import co.adityarajput.notifilter.utils.sendIntent
2324
import kotlinx.coroutines.*
2425
import kotlinx.coroutines.flow.collectLatest
@@ -167,7 +168,7 @@ class NotificationListener : NotificationListenerService() {
167168
is Action.TAP_BUTTON ->
168169
try {
169170
intents.actions.entries.find {
170-
Regex(filter.action.buttonRegex).containsMatchIn(it.key)
171+
filter.action.buttonRegex.containsMatchIn(it.key)
171172
}?.value?.send()
172173
} catch (e: Exception) {
173174
Logger.e("NotificationListener", "Failed to tap button", e)

app/src/main/java/co/adityarajput/notifilter/utils/Regex.kt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
package co.adityarajput.notifilter.utils
22

3+
import net.fellbaum.jemoji.EmojiManager
4+
5+
private const val EMOJI_PATTERN_DISPLAY = "\\p{Emoji}"
6+
private const val EMOJI_PATTERN_INTERNAL = "\uE010"
7+
8+
fun String.containsMatchIn(input: String): Boolean {
9+
var pattern = this
10+
var text = input
11+
12+
if (contains(EMOJI_PATTERN_DISPLAY)) {
13+
pattern = pattern.replace(EMOJI_PATTERN_DISPLAY, EMOJI_PATTERN_INTERNAL)
14+
text = EmojiManager.replaceAllEmojis(text, EMOJI_PATTERN_INTERNAL)
15+
}
16+
17+
return Regex(pattern).containsMatchIn(text)
18+
}
19+
20+
fun String.isValidRegex() = try {
21+
this
22+
.replace(EMOJI_PATTERN_DISPLAY, EMOJI_PATTERN_INTERNAL)
23+
.run { Regex(this).pattern == this }
24+
} catch (_: Exception) {
25+
false
26+
}
27+
28+
private const val REGEX_META_CHARACTERS = "\\.^$|?*+()[]{}"
29+
330
fun String.generateRegex() = buildString {
431
append('^')
532
for (char in this@generateRegex) {
6-
if (REGEX_META_CHARACTERS.contains(char)) {
33+
if (REGEX_META_CHARACTERS.contains(char))
734
append('\\')
8-
}
935
append(char)
1036
}
1137
append('$')
1238
}
13-
14-
private val REGEX_META_CHARACTERS =
15-
setOf('\\', '.', '^', '$', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}')

app/src/main/java/co/adityarajput/notifilter/viewmodels/UpsertFilterViewModel.kt

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import co.adityarajput.notifilter.data.Repository
1313
import co.adityarajput.notifilter.data.models.*
1414
import co.adityarajput.notifilter.services.NotificationListener
1515
import co.adityarajput.notifilter.utils.Logger
16+
import co.adityarajput.notifilter.utils.containsMatchIn
17+
import co.adityarajput.notifilter.utils.isValidRegex
1618
import kotlinx.coroutines.Dispatchers
1719
import kotlinx.coroutines.delay
1820
import kotlinx.coroutines.launch
@@ -108,26 +110,20 @@ class UpsertFilterViewModel(
108110

109111
FormPage.PATTERN -> {
110112
if (values.queryPattern.isBlank()) return FormError.BLANK_FIELDS
111-
try {
112-
Regex(values.queryPattern).pattern == values.queryPattern
113-
if (values.regexTarget == RegexTarget.AND) {
114-
if (values.secondaryQueryPattern.isBlank()) return FormError.BLANK_FIELDS
115-
Regex(values.secondaryQueryPattern).pattern == values.secondaryQueryPattern
116-
}
117-
} catch (_: Exception) {
118-
return FormError.INVALID_NOTIFICATION_REGEX
113+
114+
if (!values.queryPattern.isValidRegex()) return FormError.INVALID_NOTIFICATION_REGEX
115+
116+
if (values.regexTarget == RegexTarget.AND) {
117+
if (values.secondaryQueryPattern.isBlank()) return FormError.BLANK_FIELDS
118+
if (!values.secondaryQueryPattern.isValidRegex()) return FormError.INVALID_NOTIFICATION_REGEX
119119
}
120120
}
121121

122122
FormPage.ACTION -> {
123123
if (values.action is Action.TAP_BUTTON) {
124-
try {
125-
if (values.action.buttonRegex.isBlank()) return FormError.BLANK_FIELDS
126-
Regex(values.action.buttonRegex).pattern == values.action.buttonRegex
127-
} catch (_: Exception) {
128-
Logger.d("FiltersViewModel.getError", "Button pattern regex invalid")
129-
return FormError.INVALID_BUTTON_REGEX
130-
}
124+
if (values.action.buttonRegex.isBlank()) return FormError.BLANK_FIELDS
125+
126+
if (!values.action.buttonRegex.isValidRegex()) return FormError.INVALID_BUTTON_REGEX
131127
}
132128

133129
if (values.action is Action.DEBOUNCE && values.app == Any) {
@@ -156,15 +152,15 @@ class UpsertFilterViewModel(
156152

157153
if (
158154
regexTarget != RegexTarget.CONTENT
159-
&& !Regex(values.queryPattern).containsMatchIn(notification.title)
155+
&& !values.queryPattern.containsMatchIn(notification.title)
160156
) warnings.add(FormWarning.REGEX_DOESNT_MATCH_TITLE)
161157
if (
162158
(regexTarget == RegexTarget.CONTENT || regexTarget == RegexTarget.OR)
163-
&& !Regex(values.queryPattern).containsMatchIn(notification.content)
159+
&& !values.queryPattern.containsMatchIn(notification.content)
164160
) warnings.add(FormWarning.REGEX_DOESNT_MATCH_CONTENT)
165161
if (
166162
regexTarget == RegexTarget.AND &&
167-
!Regex(values.secondaryQueryPattern).containsMatchIn(notification.content)
163+
!values.secondaryQueryPattern.containsMatchIn(notification.content)
168164
) warnings.add(FormWarning.REGEX_DOESNT_MATCH_CONTENT)
169165

170166
return warnings

app/src/main/java/co/adityarajput/notifilter/views/screens/UpsertFilterScreen.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,6 @@ private fun PatternPage(viewModel: UpsertFilterViewModel) {
405405
unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer,
406406
disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer,
407407
),
408-
singleLine = true,
409408
)
410409
AnimatedVisibility(viewModel.state.values.regexTarget == RegexTarget.AND) {
411410
OutlinedTextField(
@@ -425,7 +424,6 @@ private fun PatternPage(viewModel: UpsertFilterViewModel) {
425424
unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer,
426425
disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer,
427426
),
428-
singleLine = true,
429427
)
430428
}
431429
Text(
@@ -548,7 +546,6 @@ private fun ColumnScope.ActionPage(viewModel: UpsertFilterViewModel) {
548546
unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer,
549547
disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer,
550548
),
551-
singleLine = true,
552549
)
553550
if (viewModel.state.error == FormError.INVALID_BUTTON_REGEX) ErrorText(R.string.invalid_regex)
554551
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<resources>
33
<string name="app_name" translatable="false">NotiFilter</string>
44
<string name="app_name_launcher" translatable="false">NotiFilter</string>
5-
<string name="app_version" translatable="false">4.8.1</string>
5+
<string name="app_version" translatable="false">4.9.0</string>
66

77
<!-- region FiltersScreen -->
88
<string name="no_filters">No filters added.\nTap + to get started.</string>

gradle/libs.versions.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22
aboutlibraries = "13.2.1"
33
agp = "8.13.2"
44
kotlin = "2.3.10"
5-
coreKtx = "1.17.0"
5+
coreKtx = "1.18.0"
66
junit = "4.13.2"
77
junitVersion = "1.3.0"
88
espressoCore = "3.7.0"
99
kotlinxSerializationJson = "1.10.0"
1010
lifecycleRuntimeKtx = "2.10.0"
11-
activityCompose = "1.12.4"
11+
activityCompose = "1.13.0"
1212
composeBom = "2026.02.00"
1313
appcompat = "1.7.1"
1414
navigation = "2.9.7"
1515
room = "2.8.4"
1616
composeMaterial = "1.5.6"
1717
glance = "1.2.0-rc01"
1818
ksp = "2.3.4"
19+
jemoji = "1.7.6"
1920

2021
[libraries]
2122
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -45,6 +46,7 @@ androidx-glance-preview = { group = "androidx.glance", name = "glance-preview",
4546
androidx-glance-appwidget-preview = { group = "androidx.glance", name = "glance-appwidget-preview", version.ref = "glance" }
4647
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
4748
aboutlibraries-compose = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "aboutlibraries" }
49+
jemoji = { group = "net.fellbaum", name = "jemoji", version.ref = "jemoji" }
4850

4951
[plugins]
5052
aboutlibraries-android = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutlibraries" }

metadata/en-US/changelogs/28.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
• fix: Make regex input fields multi-line
2+
• feat: Allow emoji-matching via a special pattern
3+
4+
The default regex implementation available to us cannot be used to match arbitrary emojis, so a special pattern has been added to simulate this behavior. For details, visit the Tips page of the project wiki.

0 commit comments

Comments
 (0)