Skip to content

Commit 2398042

Browse files
committed
Add "DISMISS_STALE" action
1 parent 1cee877 commit 2398042

9 files changed

Lines changed: 114 additions & 21 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ NotiFilter listens to all device notifications and quietly manages those that ma
2828
6. Debounce it ❄
2929
7. Mute it 🔇
3030
8. Play an alert 🔔
31+
9. Disable DND mode 🔊
32+
10. Remove after a delay ⏲️
3133
- **Schedule** - Choose when filters run (e.g. only during work hours) ⏰
32-
- **History** - Recently dismissed notifications are stored (locally), just in case 😉
34+
- **History** - Recently dismissed notifications are stored locally, for reference and retrieval 🧾
3335
- **Widget** - Configure filters to send notifications to a home screen widget 📱
3436
- **Export/Import** - Backup or transfer your filters as JSON files 📂
3537
- **Free, open-source & private**

app/build.gradle.kts

Lines changed: 2 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 = 25
23-
versionName = "4.7.0"
22+
versionCode = 26
23+
versionName = "4.8.0"
2424

2525
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2626
}

app/src/main/java/co/adityarajput/notifilter/data/AppContainer.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ class AppContainer(private val context: Context) {
9898
RegexTarget.OR,
9999
historyEnabled = false,
100100
),
101+
Filter(
102+
App("Twitch", "tv.twitch.android.app"),
103+
"live",
104+
Action.DISMISS_STALE(60),
105+
RegexTarget.OR,
106+
historyEnabled = false,
107+
),
101108
)
102109
repository.upsert(
103110
Notification(

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ sealed class Action {
3737
@Serializable
3838
data class DISTURB(val pauseLength: Int) : Action()
3939

40+
@Serializable
41+
data class DISMISS_STALE(val retentionLength: Int) : Action()
42+
4043
@Composable
4144
fun verb() = when (this) {
4245
is DISMISS -> stringResource(R.string.dismiss_short)
@@ -60,6 +63,11 @@ sealed class Action {
6063
R.string.disturb_short,
6164
pluralStringResource(R.plurals.minute, pauseLength, pauseLength),
6265
)
66+
67+
is DISMISS_STALE -> stringResource(
68+
R.string.dismiss_stale_short,
69+
pluralStringResource(R.plurals.minute, retentionLength, retentionLength),
70+
)
6371
}
6472

6573
@Composable
@@ -74,6 +82,7 @@ sealed class Action {
7482
is MUTE -> R.string.mute_long
7583
is ALERT -> R.string.alert_long
7684
is DISTURB -> R.string.disturb_long
85+
is DISMISS_STALE -> R.string.dismiss_stale_long
7786
},
7887
)
7988

@@ -83,7 +92,7 @@ sealed class Action {
8392
val entries by lazy {
8493
listOf(
8594
DISMISS, TAP_NOTIFICATION, TAP_BUTTON(""), BATCH(3),
86-
DELAY, DEBOUNCE(2), MUTE, ALERT, DISTURB(5),
95+
DELAY, DEBOUNCE(2), MUTE, ALERT, DISTURB(5), DISMISS_STALE(10),
8796
)
8897
}
8998

@@ -114,6 +123,10 @@ sealed class Action {
114123
value.removePrefix("DISTURB(pauseLength=").removeSuffix(")").toInt(),
115124
)
116125

126+
value.startsWith("DISMISS_STALE") -> DISMISS_STALE(
127+
value.removePrefix("DISMISS_STALE(retentionLength=").removeSuffix(")").toInt(),
128+
)
129+
117130
else -> {
118131
Logger.e("Action.fromString", value)
119132
throw IllegalArgumentException("Can't convert value to Action, unknown value: $value")

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

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,7 @@ class NotificationListener : NotificationListenerService() {
151151
Logger.i("NotificationListener", "Matched $filter")
152152

153153
when (filter.action) {
154-
is Action.DISMISS ->
155-
if (sbn.isClearable) {
156-
try {
157-
cancelNotification(sbn.key)
158-
} catch (e: Throwable) {
159-
Logger.e(
160-
"NotificationListener",
161-
"Failed to dismiss notification",
162-
e,
163-
)
164-
}
165-
} else {
166-
Logger.d("NotificationListener", "Is unclearable")
167-
snoozeNotification(sbn.key, 5 * 60 * 60 * 1000L)
168-
}
154+
is Action.DISMISS -> dismissOrSnoozeFor5Hours(sbn)
169155

170156
is Action.TAP_NOTIFICATION ->
171157
try {
@@ -270,6 +256,20 @@ class NotificationListener : NotificationListenerService() {
270256
cooldowns += filter.id to (System.currentTimeMillis() + filter.action.pauseLength * 60 * 1000L)
271257
}
272258
}
259+
260+
is Action.DISMISS_STALE -> {
261+
serviceScope.launch {
262+
var retentionLength = filter.action.retentionLength * 60 * 1000L
263+
if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
264+
// INFO: While debugging, retain for shorter intervals
265+
retentionLength /= 5
266+
}
267+
268+
Logger.d("NotificationListener", "Waiting $retentionLength ms before removing")
269+
delay(retentionLength)
270+
dismissOrSnoozeFor5Hours(sbn)
271+
}
272+
}
273273
}
274274

275275
serviceScope.launch {
@@ -286,6 +286,23 @@ class NotificationListener : NotificationListenerService() {
286286
}
287287
}
288288

289+
private fun dismissOrSnoozeFor5Hours(sbn: StatusBarNotification) {
290+
if (sbn.isClearable) {
291+
try {
292+
cancelNotification(sbn.key)
293+
} catch (e: Throwable) {
294+
Logger.e(
295+
"NotificationListener",
296+
"Failed to dismiss notification",
297+
e,
298+
)
299+
}
300+
} else {
301+
Logger.d("NotificationListener", "Is unclearable")
302+
snoozeNotification(sbn.key, 5 * 60 * 60 * 1000L)
303+
}
304+
}
305+
289306
private fun muteNotificationsWhileCooldown(filter: Filter) {
290307
serviceScope.launch {
291308
delay(NOTIFICATION_SOUND_DURATION) // INFO: Wait for original notification sound

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,56 @@ private fun ColumnScope.ActionPage(viewModel: UpsertFilterViewModel) {
741741
}
742742
}
743743
}
744+
AnimatedVisibility(it is Action.DISMISS_STALE && viewModel.state.values.action is Action.DISMISS_STALE) {
745+
Column(
746+
Modifier.fillMaxWidth(),
747+
Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)),
748+
) {
749+
Row(
750+
Modifier.fillMaxWidth(),
751+
Arrangement.Center,
752+
Alignment.CenterVertically,
753+
) {
754+
val retentionLength =
755+
(viewModel.state.values.action as? Action.DISMISS_STALE)?.retentionLength
756+
?: 10
757+
IconButton(
758+
{
759+
viewModel.updateForm(
760+
viewModel.state.page,
761+
viewModel.state.values.copy(action = Action.DISMISS_STALE((retentionLength - 5))),
762+
)
763+
},
764+
enabled = retentionLength > 5,
765+
) {
766+
Icon(
767+
painterResource(R.drawable.remove),
768+
contentDescription = stringResource(R.string.alttext_subtract),
769+
)
770+
}
771+
Text(
772+
stringResource(
773+
R.string.retention_length,
774+
retentionLength.toString().padStart(2, '0'),
775+
),
776+
)
777+
IconButton(
778+
{
779+
viewModel.updateForm(
780+
viewModel.state.page,
781+
viewModel.state.values.copy(action = Action.DISMISS_STALE((retentionLength + 5))),
782+
)
783+
},
784+
enabled = retentionLength < 60,
785+
) {
786+
Icon(
787+
painterResource(R.drawable.add),
788+
contentDescription = stringResource(R.string.alttext_add),
789+
)
790+
}
791+
}
792+
}
793+
}
744794
}
745795
HorizontalDivider()
746796
Text(

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

Lines changed: 4 additions & 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.7.0</string>
5+
<string name="app_version" translatable="false">4.8.0</string>
66

77
<!-- region FiltersScreen -->
88
<string name="no_filters">No filters added.\nTap + to get started.</string>
@@ -19,6 +19,7 @@
1919
<string name="mute_short">mute notification</string>
2020
<string name="alert_short">play an alert</string>
2121
<string name="disturb_short">disable DND for %1$s</string>
22+
<string name="dismiss_stale_short">dismiss after %1$s</string>
2223
<string name="any_app">Any app</string>
2324
<string name="history_disabled">history disabled</string>
2425
<string name="filter_disabled">filter disabled</string>
@@ -113,6 +114,8 @@
113114
<string name="disturb_long">Disable \"Do Not Disturb\"</string>
114115
<string name="pause_length">Enable after %1$s min(s)</string>
115116
<string name="notification_policy_permission_description">NotiFilter requires access to the Notification Policy to change DND status</string>
117+
<string name="dismiss_stale_long">Remove stale notifications</string>
118+
<string name="retention_length">Dismiss after %1$s min(s)</string>
116119
<string name="display_options">Choose where to display caught notification</string>
117120
<string name="describe_history_screen">In-app History screen</string>
118121
<string name="describe_widget">Homescreen Log widget</string>

metadata/en-US/changelogs/26.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: Add "DISMISS_STALE" action
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p>NotiFilter listens to all device notifications and quietly manages those that match your filters.</p><p><strong>Features:</strong><ul><li>Filters: Use regex to precisely target annoying notifications from each app.</li><li>Actions: Choose what to do with the filtered notifications - dismiss, tap, delay, batch, debounce, mute, or alert.</li><li>Schedule: Choose when filters run (e.g. only during work hours)</li><li>History: Recently dismissed notifications are stored (locally), just in case.</li><li>Widget: Configure filters to send notifications to a home screen widget.</li><li>Export/Import: Backup or transfer your filters as JSON files.</li><li>Private: Fully offline; your data never leaves your device.</li><li>Lightweight: Runs in the background with minimal battery and memory usage.</li></ul></p>
1+
<p>NotiFilter listens to all device notifications and quietly manages those that match your filters.</p><p><strong>Features:</strong><ul><li>Filters: Use regex to precisely target annoying notifications from each app.</li><li>Actions: Choose what to do with the filtered notifications - dismiss, tap, delay, batch, debounce, mute, alert, disable DND, or remove stale.</li><li>Schedule: Choose when filters run (e.g. only during work hours)</li><li>History: Recently dismissed notifications are stored locally, for reference and retrieval.</li><li>Widget: Configure filters to send notifications to a home screen widget.</li><li>Export/Import: Backup or transfer your filters as JSON files.</li><li>Private: Fully offline; your data never leaves your device.</li><li>Lightweight: Runs in the background with minimal battery and memory usage.</li></ul></p>

0 commit comments

Comments
 (0)