From 196ec19495e6e96e2d0cd535116bd2dea8b3903e Mon Sep 17 00:00:00 2001 From: William Harris Date: Tue, 7 Apr 2026 03:55:06 +0000 Subject: [PATCH 1/6] fix: pin menu message --- .../com/github/quarck/calnotify/ui/MainActivityModern.kt | 5 ++++- android/app/src/main/res/values/strings.xml | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt index 9d44d0b2..daa666db 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt @@ -230,9 +230,11 @@ class MainActivityModern : MainActivityBase() { // Show pin all / unpin all only for fragments that support it val supportsPinAll = currentFragment?.supportsPinAll() == true + val hasFilters = filterState.hasActiveFilters() || !currentFragment?.getSearchQuery().isNullOrEmpty() val pinAllMenuItem = menu.findItem(R.id.action_pin_all) pinAllMenuItem?.isVisible = supportsPinAll pinAllMenuItem?.isEnabled = currentFragment?.anyForPinAll() == true + pinAllMenuItem?.title = getString(if (hasFilters) R.string.pin_all_filtered else R.string.pin_all) val unpinAllMenuItem = menu.findItem(R.id.action_unpin_all) unpinAllMenuItem?.isVisible = supportsPinAll unpinAllMenuItem?.isEnabled = currentFragment?.anyForUnpinAll() == true @@ -408,8 +410,9 @@ class MainActivityModern : MainActivityBase() { } private fun onPinAll() { + val hasFilters = filterState.hasActiveFilters() || !getCurrentSearchableFragment()?.getSearchQuery().isNullOrEmpty() AlertDialog.Builder(this) - .setMessage(R.string.pin_all_events_question) + .setMessage(if (hasFilters) R.string.pin_all_filtered_events_question else R.string.pin_all_events_question) .setCancelable(false) .setPositiveButton(android.R.string.yes) { _, _ -> doPinAll() diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 623bb2b3..61b4b355 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -400,9 +400,11 @@ Notification is pinned Pin Unpin - Pin all visible + Pin all + Pin all filtered Unpin all - Pin all visible events?\nPinned events are excluded from batch snooze. + Pin all events?\nPinned events are excluded from batch snooze. + Pin all filtered events?\nPinned events are excluded from batch snooze. Unpin all pinned events? Pinned Unpinned From b403781ce964266b37e26a8910f0813de815d3fa Mon Sep 17 00:00:00 2001 From: William Harris Date: Tue, 7 Apr 2026 04:00:48 +0000 Subject: [PATCH 2/6] fix: mute message --- .../com/github/quarck/calnotify/ui/MainActivityModern.kt | 7 +++++-- android/app/src/main/res/values/strings.xml | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt index daa666db..3735bc86 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt @@ -216,11 +216,14 @@ class MainActivityModern : MainActivityBase() { // Custom quiet hours is deprecated menu.findItem(R.id.action_custom_quiet_interval)?.isVisible = false + val hasFilters = filterState.hasActiveFilters() || !currentFragment?.getSearchQuery().isNullOrEmpty() + // Show mute all only for fragments that support it (Active events) val muteAllMenuItem = menu.findItem(R.id.action_mute_all) val supportsMuteAll = currentFragment?.supportsMuteAll() == true muteAllMenuItem?.isVisible = supportsMuteAll && settings.enableNotificationMute muteAllMenuItem?.isEnabled = currentFragment?.anyForMuteAll() == true + muteAllMenuItem?.title = getString(if (hasFilters) R.string.mute_all_filtered else R.string.mute_all) // Show dismiss all only for fragments that support it (Active events) val dismissAllMenuItem = menu.findItem(R.id.action_dismiss_all) @@ -230,7 +233,6 @@ class MainActivityModern : MainActivityBase() { // Show pin all / unpin all only for fragments that support it val supportsPinAll = currentFragment?.supportsPinAll() == true - val hasFilters = filterState.hasActiveFilters() || !currentFragment?.getSearchQuery().isNullOrEmpty() val pinAllMenuItem = menu.findItem(R.id.action_pin_all) pinAllMenuItem?.isVisible = supportsPinAll pinAllMenuItem?.isEnabled = currentFragment?.anyForPinAll() == true @@ -392,8 +394,9 @@ class MainActivityModern : MainActivityBase() { } private fun onMuteAll() { + val hasFilters = filterState.hasActiveFilters() || !getCurrentSearchableFragment()?.getSearchQuery().isNullOrEmpty() AlertDialog.Builder(this) - .setMessage(R.string.mute_all_events_question) + .setMessage(if (hasFilters) R.string.mute_all_filtered_events_question else R.string.mute_all_events_question) .setCancelable(false) .setPositiveButton(android.R.string.yes) { _, _ -> doMuteAll() diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 61b4b355..8dfa5be4 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -394,8 +394,10 @@ Un-Mute Mute - Mute all visible - Mute all visible events? (Tasks excluded) + Mute all + Mute all filtered + Mute all events? (Tasks excluded) + Mute all filtered events? (Tasks excluded) Notification is pinned Pin From fa17d0c5ae6b823b8b3487c8b7c4c1e852283c54 Mon Sep 17 00:00:00 2001 From: William Harris Date: Tue, 7 Apr 2026 04:07:01 +0000 Subject: [PATCH 3/6] fix: event pinning fast follows --- .../calnotify/ui/ViewEventActivityNoRecents.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/ViewEventActivityNoRecents.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/ViewEventActivityNoRecents.kt index d7a2e784..fcee2a08 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/ViewEventActivityNoRecents.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/ViewEventActivityNoRecents.kt @@ -527,14 +527,16 @@ open class ViewEventActivityNoRecents : AppCompatActivity() { } private fun togglePinForEvent(pinned: Boolean) { - getEventsStorage(this).use { db -> - val current = db.getEvent(event.eventId, event.instanceStartTime) - if (current != null) { - current.isPinned = pinned - db.updateEvent(current) + event.isPinned = pinned + background { + getEventsStorage(this).use { db -> + val current = db.getEvent(event.eventId, event.instanceStartTime) + if (current != null) { + current.isPinned = pinned + db.updateEvent(current) + } } } - event.isPinned = pinned } private fun formatPreset(preset: Long): String { From 5921e5f2e1b74e3324e43b8f41a45658edbb9ab9 Mon Sep 17 00:00:00 2001 From: William Harris Date: Tue, 7 Apr 2026 04:58:36 +0000 Subject: [PATCH 4/6] fix: multi pin and mute` --- .../calnotify/app/ApplicationController.kt | 56 +++++++++++-- .../quarck/calnotify/ui/MainActivityModern.kt | 12 ++- ...pplicationControllerCoreRobolectricTest.kt | 84 ++++++++++++++++++- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt index fcae9937..1e46faef 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt @@ -1122,12 +1122,25 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler } } - fun muteAllVisibleEvents(context: Context) { - + fun muteAllVisibleEvents( + context: Context, + searchQuery: String? = null, + filterState: FilterState? = null + ) { + val now = clock.currentTimeMillis() getEventsStorage(context).use { db -> val eventsToMute = db.events.filter { - event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isTask + event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isTask && + (searchQuery?.let { query -> + event.title.contains(query, ignoreCase = true) || + event.desc.contains(query, ignoreCase = true) + } ?: true) && + (filterState?.let { filter -> + filter.matchesCalendar(event) && + filter.matchesStatus(event) && + filter.matchesTime(event, now) + } ?: true) } if (eventsToMute.isNotEmpty()) { @@ -1146,10 +1159,24 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler } } - fun pinAllVisibleEvents(context: Context) { + fun pinAllVisibleEvents( + context: Context, + searchQuery: String? = null, + filterState: FilterState? = null + ) { + val now = clock.currentTimeMillis() getEventsStorage(context).use { db -> val eventsToPin = db.events.filter { - event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isPinned + event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isPinned && + (searchQuery?.let { query -> + event.title.contains(query, ignoreCase = true) || + event.desc.contains(query, ignoreCase = true) + } ?: true) && + (filterState?.let { filter -> + filter.matchesCalendar(event) && + filter.matchesStatus(event) && + filter.matchesTime(event, now) + } ?: true) } if (eventsToPin.isNotEmpty()) { val pinnedEvents = eventsToPin.map { it.isPinned = true; it } @@ -1158,9 +1185,24 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler } } - fun unpinAllVisibleEvents(context: Context) { + fun unpinAllVisibleEvents( + context: Context, + searchQuery: String? = null, + filterState: FilterState? = null + ) { + val now = clock.currentTimeMillis() getEventsStorage(context).use { db -> - val eventsToUnpin = db.events.filter { event -> event.isPinned } + val eventsToUnpin = db.events.filter { event -> event.isPinned && + (searchQuery?.let { query -> + event.title.contains(query, ignoreCase = true) || + event.desc.contains(query, ignoreCase = true) + } ?: true) && + (filterState?.let { filter -> + filter.matchesCalendar(event) && + filter.matchesStatus(event) && + filter.matchesTime(event, now) + } ?: true) + } if (eventsToUnpin.isNotEmpty()) { val unpinnedEvents = eventsToUnpin.map { it.isPinned = false; it } db.updateEvents(unpinnedEvents) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt index 3735bc86..375ea959 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt @@ -407,7 +407,9 @@ class MainActivityModern : MainActivityBase() { } private fun doMuteAll() { - ApplicationController.muteAllVisibleEvents(this) + val searchQuery = getCurrentSearchableFragment()?.getSearchQuery() + val currentFilterState = getCurrentFilterState() + ApplicationController.muteAllVisibleEvents(this, searchQuery, currentFilterState) getCurrentSearchableFragment()?.onMuteAllComplete() invalidateOptionsMenu() } @@ -426,7 +428,9 @@ class MainActivityModern : MainActivityBase() { } private fun doPinAll() { - ApplicationController.pinAllVisibleEvents(this) + val searchQuery = getCurrentSearchableFragment()?.getSearchQuery() + val currentFilterState = getCurrentFilterState() + ApplicationController.pinAllVisibleEvents(this, searchQuery, currentFilterState) getCurrentSearchableFragment()?.onPinAllComplete() invalidateOptionsMenu() } @@ -444,7 +448,9 @@ class MainActivityModern : MainActivityBase() { } private fun doUnpinAll() { - ApplicationController.unpinAllVisibleEvents(this) + val searchQuery = getCurrentSearchableFragment()?.getSearchQuery() + val currentFilterState = getCurrentFilterState() + ApplicationController.unpinAllVisibleEvents(this, searchQuery, currentFilterState) getCurrentSearchableFragment()?.onPinAllComplete() invalidateOptionsMenu() } diff --git a/android/app/src/test/java/com/github/quarck/calnotify/app/ApplicationControllerCoreRobolectricTest.kt b/android/app/src/test/java/com/github/quarck/calnotify/app/ApplicationControllerCoreRobolectricTest.kt index 2e9ff30f..7e6e6735 100644 --- a/android/app/src/test/java/com/github/quarck/calnotify/app/ApplicationControllerCoreRobolectricTest.kt +++ b/android/app/src/test/java/com/github/quarck/calnotify/app/ApplicationControllerCoreRobolectricTest.kt @@ -84,6 +84,7 @@ class ApplicationControllerCoreRobolectricTest { private fun createTestEvent( eventId: Long = 1L, + calendarId: Long = 1L, instanceStartTime: Long = baseTime, snoozedUntil: Long = 0L, isMuted: Boolean = false, @@ -91,7 +92,7 @@ class ApplicationControllerCoreRobolectricTest { lastStatusChangeTime: Long = baseTime ): EventAlertRecord { val event = EventAlertRecord( - calendarId = 1L, + calendarId = calendarId, eventId = eventId, isAllDay = false, isRepeating = false, @@ -462,6 +463,87 @@ class ApplicationControllerCoreRobolectricTest { assertFalse("No events should be pinned after unpin all", events.any { it.isPinned }) } + // === filter-aware muteAllVisibleEvents tests === + + @Test + fun testMuteAllVisibleEvents_respectsSearchQuery() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1).apply { title = "Meeting with Alice" }) + mockEventsStorage.addEvent(createTestEvent(eventId = 2).apply { title = "Lunch with Bob" }) + + ApplicationController.muteAllVisibleEvents(context, searchQuery = "Alice") + + val events = mockEventsStorage.events + assertTrue("Matching event should be muted", events.find { it.eventId == 1L }?.isMuted == true) + assertFalse("Non-matching event should NOT be muted", events.find { it.eventId == 2L }?.isMuted == true) + } + + @Test + fun testMuteAllVisibleEvents_respectsFilterState() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1, calendarId = 10L)) + mockEventsStorage.addEvent(createTestEvent(eventId = 2, calendarId = 20L)) + + val filter = FilterState(selectedCalendarIds = setOf(10L)) + ApplicationController.muteAllVisibleEvents(context, filterState = filter) + + val events = mockEventsStorage.events + assertTrue("Filtered-in event should be muted", events.find { it.eventId == 1L }?.isMuted == true) + assertFalse("Filtered-out event should NOT be muted", events.find { it.eventId == 2L }?.isMuted == true) + } + + // === filter-aware pinAllVisibleEvents tests === + + @Test + fun testPinAllVisibleEvents_respectsSearchQuery() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1).apply { title = "Meeting with Alice" }) + mockEventsStorage.addEvent(createTestEvent(eventId = 2).apply { title = "Lunch with Bob" }) + + ApplicationController.pinAllVisibleEvents(context, searchQuery = "Alice") + + val events = mockEventsStorage.events + assertTrue("Matching event should be pinned", events.find { it.eventId == 1L }?.isPinned == true) + assertFalse("Non-matching event should NOT be pinned", events.find { it.eventId == 2L }?.isPinned == true) + } + + @Test + fun testPinAllVisibleEvents_respectsFilterState() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1, calendarId = 10L)) + mockEventsStorage.addEvent(createTestEvent(eventId = 2, calendarId = 20L)) + + val filter = FilterState(selectedCalendarIds = setOf(10L)) + ApplicationController.pinAllVisibleEvents(context, filterState = filter) + + val events = mockEventsStorage.events + assertTrue("Filtered-in event should be pinned", events.find { it.eventId == 1L }?.isPinned == true) + assertFalse("Filtered-out event should NOT be pinned", events.find { it.eventId == 2L }?.isPinned == true) + } + + // === filter-aware unpinAllVisibleEvents tests === + + @Test + fun testUnpinAllVisibleEvents_respectsSearchQuery() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1).apply { isPinned = true; title = "Meeting with Alice" }) + mockEventsStorage.addEvent(createTestEvent(eventId = 2).apply { isPinned = true; title = "Lunch with Bob" }) + + ApplicationController.unpinAllVisibleEvents(context, searchQuery = "Alice") + + val events = mockEventsStorage.events + assertFalse("Matching event should be unpinned", events.find { it.eventId == 1L }?.isPinned == true) + assertTrue("Non-matching event should stay pinned", events.find { it.eventId == 2L }?.isPinned == true) + } + + @Test + fun testUnpinAllVisibleEvents_respectsFilterState() { + mockEventsStorage.addEvent(createTestEvent(eventId = 1, calendarId = 10L).apply { isPinned = true }) + mockEventsStorage.addEvent(createTestEvent(eventId = 2, calendarId = 20L).apply { isPinned = true }) + + val filter = FilterState(selectedCalendarIds = setOf(10L)) + ApplicationController.unpinAllVisibleEvents(context, filterState = filter) + + val events = mockEventsStorage.events + assertFalse("Filtered-in event should be unpinned", events.find { it.eventId == 1L }?.isPinned == true) + assertTrue("Filtered-out event should stay pinned", events.find { it.eventId == 2L }?.isPinned == true) + } + // === onEventAlarm tests === @Test From 068a047cfa33836b97e9cbb9a2c32f8335ec2f0f Mon Sep 17 00:00:00 2001 From: William Harris Date: Thu, 9 Apr 2026 04:09:47 +0000 Subject: [PATCH 5/6] fix: address bug bot --- .../github/quarck/calnotify/app/ApplicationController.kt | 9 ++++++--- .../com/github/quarck/calnotify/ui/MainActivityModern.kt | 4 +++- android/app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt index 1e46faef..7f13d271 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt @@ -1139,7 +1139,8 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler (filterState?.let { filter -> filter.matchesCalendar(event) && filter.matchesStatus(event) && - filter.matchesTime(event, now) + filter.matchesTime(event, now) && + filter.matchesSnoozedUntil(event, now) } ?: true) } @@ -1175,7 +1176,8 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler (filterState?.let { filter -> filter.matchesCalendar(event) && filter.matchesStatus(event) && - filter.matchesTime(event, now) + filter.matchesTime(event, now) && + filter.matchesSnoozedUntil(event, now) } ?: true) } if (eventsToPin.isNotEmpty()) { @@ -1200,7 +1202,8 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler (filterState?.let { filter -> filter.matchesCalendar(event) && filter.matchesStatus(event) && - filter.matchesTime(event, now) + filter.matchesTime(event, now) && + filter.matchesSnoozedUntil(event, now) } ?: true) } if (eventsToUnpin.isNotEmpty()) { diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt index 375ea959..7fad27bd 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt @@ -240,6 +240,7 @@ class MainActivityModern : MainActivityBase() { val unpinAllMenuItem = menu.findItem(R.id.action_unpin_all) unpinAllMenuItem?.isVisible = supportsPinAll unpinAllMenuItem?.isEnabled = currentFragment?.anyForUnpinAll() == true + unpinAllMenuItem?.title = getString(if (hasFilters) R.string.unpin_all_filtered else R.string.unpin_all) // Show snooze all only for fragments that support it (Active events) val snoozeAllMenuItem = menu.findItem(R.id.action_snooze_all) @@ -436,8 +437,9 @@ class MainActivityModern : MainActivityBase() { } private fun onUnpinAll() { + val hasFilters = filterState.hasActiveFilters() || !getCurrentSearchableFragment()?.getSearchQuery().isNullOrEmpty() AlertDialog.Builder(this) - .setMessage(R.string.unpin_all_events_question) + .setMessage(if (hasFilters) R.string.unpin_all_filtered_events_question else R.string.unpin_all_events_question) .setCancelable(false) .setPositiveButton(android.R.string.yes) { _, _ -> doUnpinAll() diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 8dfa5be4..de71eec4 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -405,9 +405,11 @@ Pin all Pin all filtered Unpin all + Unpin all filtered Pin all events?\nPinned events are excluded from batch snooze. Pin all filtered events?\nPinned events are excluded from batch snooze. Unpin all pinned events? + Unpin all filtered pinned events? Pinned Unpinned From a07c51cddbbddef56f0966613f9561cd999d8b57 Mon Sep 17 00:00:00 2001 From: William Harris Date: Thu, 9 Apr 2026 04:41:14 +0000 Subject: [PATCH 6/6] fix: code cleanup --- .../calnotify/app/ApplicationController.kt | 46 ++----------------- .../github/quarck/calnotify/ui/FilterState.kt | 25 +++++++++- 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt index 7f13d271..d1323e62 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/app/ApplicationController.kt @@ -943,19 +943,8 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler ): SnoozeResult? { val now = clock.currentTimeMillis() return snoozeEvents(context, { event -> - // Pinned events are excluded from batch snooze !event.isPinned && - // Search query filter (existing behavior) - (searchQuery?.let { query -> - event.title.contains(query, ignoreCase = true) || - event.desc.contains(query, ignoreCase = true) - } ?: true) && - // FilterState filters - (filterState?.let { filter -> - filter.matchesCalendar(event) && - filter.matchesStatus(event) && - filter.matchesTime(event, now) - } ?: true) + FilterState.matchesSearchAndFilters(event, searchQuery, filterState, now) }, snoozeDelay, isChange, onlySnoozeVisible) } @@ -1132,16 +1121,7 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler db -> val eventsToMute = db.events.filter { event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isTask && - (searchQuery?.let { query -> - event.title.contains(query, ignoreCase = true) || - event.desc.contains(query, ignoreCase = true) - } ?: true) && - (filterState?.let { filter -> - filter.matchesCalendar(event) && - filter.matchesStatus(event) && - filter.matchesTime(event, now) && - filter.matchesSnoozedUntil(event, now) - } ?: true) + FilterState.matchesSearchAndFilters(event, searchQuery, filterState, now) } if (eventsToMute.isNotEmpty()) { @@ -1169,16 +1149,7 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler getEventsStorage(context).use { db -> val eventsToPin = db.events.filter { event -> (event.snoozedUntil == 0L) && event.isNotSpecial && !event.isPinned && - (searchQuery?.let { query -> - event.title.contains(query, ignoreCase = true) || - event.desc.contains(query, ignoreCase = true) - } ?: true) && - (filterState?.let { filter -> - filter.matchesCalendar(event) && - filter.matchesStatus(event) && - filter.matchesTime(event, now) && - filter.matchesSnoozedUntil(event, now) - } ?: true) + FilterState.matchesSearchAndFilters(event, searchQuery, filterState, now) } if (eventsToPin.isNotEmpty()) { val pinnedEvents = eventsToPin.map { it.isPinned = true; it } @@ -1195,16 +1166,7 @@ object ApplicationController : ApplicationControllerInterface, EventMovedHandler val now = clock.currentTimeMillis() getEventsStorage(context).use { db -> val eventsToUnpin = db.events.filter { event -> event.isPinned && - (searchQuery?.let { query -> - event.title.contains(query, ignoreCase = true) || - event.desc.contains(query, ignoreCase = true) - } ?: true) && - (filterState?.let { filter -> - filter.matchesCalendar(event) && - filter.matchesStatus(event) && - filter.matchesTime(event, now) && - filter.matchesSnoozedUntil(event, now) - } ?: true) + FilterState.matchesSearchAndFilters(event, searchQuery, filterState, now) } if (eventsToUnpin.isNotEmpty()) { val unpinnedEvents = eventsToUnpin.map { it.isPinned = false; it } diff --git a/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt b/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt index db767b83..3e153a58 100644 --- a/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt +++ b/android/app/src/main/java/com/github/quarck/calnotify/ui/FilterState.kt @@ -90,6 +90,24 @@ data class FilterState( ) { companion object { + /** + * Checks if an event matches the given search query and filter state. + * Handles nullable parameters: null searchQuery or filterState means "match all". + */ + fun matchesSearchAndFilters( + event: EventAlertRecord, + searchQuery: String?, + filterState: FilterState?, + now: Long + ): Boolean { + if (searchQuery != null && + !event.title.contains(searchQuery, ignoreCase = true) && + !event.desc.contains(searchQuery, ignoreCase = true)) { + return false + } + return filterState?.matchesEvent(event, now) ?: true + } + private const val BUNDLE_CALENDAR_IDS = "filter_calendar_ids" private const val BUNDLE_CALENDAR_NULL = "filter_calendar_null" private const val BUNDLE_STATUS_FILTERS = "filter_status" @@ -232,7 +250,12 @@ data class FilterState( if (selectedCalendarIds == null) return true // No filter = show all return event.calendarId in selectedCalendarIds // Empty set = show none } - + + /** Check if an event matches all active filters */ + fun matchesEvent(event: EventAlertRecord, now: Long): Boolean { + return filterEvents(listOf(event), now).isNotEmpty() + } + /** * Filter events by specified filter types. * @param events List of events to filter