From 621f0082e3da426ac15c5fc4db30c6915299b538 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 00:03:17 +0300 Subject: [PATCH 1/7] feat: support removing toasts and toast groups --- src/windows_toasts/toast.py | 4 ++-- src/windows_toasts/toasters.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/windows_toasts/toast.py b/src/windows_toasts/toast.py index f06b327..092ed88 100644 --- a/src/windows_toasts/toast.py +++ b/src/windows_toasts/toast.py @@ -34,7 +34,7 @@ class Toast: expiration_time: Optional[datetime.datetime] """The time for the toast to expire on in the action center. If it is on-screen, nothing will happen""" group: Optional[str] - """A generic identifier, where you can assign groups like "wallPosts", "messages", "friendRequests", etc.""" + """An internal identifier, where you can assign groups like "wallPosts", "messages", "friendRequests", etc.""" scenario: ToastScenario """Scenario for the toast""" suppress_popup: bool @@ -60,7 +60,7 @@ class Toast: text_fields: list[Optional[str]] """Various text fields""" tag: str - """uuid of a tag for the toast""" + """Unique tag for the toast, automatically set as a UUID""" updates: int """Number of times the toast has been updated; mostly for internal use""" _launch_action: Optional[str] diff --git a/src/windows_toasts/toasters.py b/src/windows_toasts/toasters.py index bf4df7a..fbe4e60 100644 --- a/src/windows_toasts/toasters.py +++ b/src/windows_toasts/toasters.py @@ -64,8 +64,8 @@ def _build_toast_notification(toast: Toast, toastNotification: ToastNotification :rtype: ToastNotificationT """ toastNotification.tag = toast.tag - if toast.group is not None: - toastNotification.group = toast.group + # Group, a non-empty string, is required for some functionality. If none is provided, use the tag + toastNotification.group = toast.group or toast.tag if toast.expiration_time is not None: toastNotification.expiration_time = toast.expiration_time @@ -208,6 +208,21 @@ def clear_scheduled_toasts(self) -> None: for toast in scheduledToasts: self.toastNotifier.remove_from_schedule(toast) + def remove_toast(self, toast: Toast) -> None: + """ + Removes an individual popped toast + """ + # Is fetching toastHistory expensive? Should this be stored in an instance variable? + toastHistory: ToastNotificationHistory = ToastNotificationManager.history + toastHistory.remove(toast.tag, toast.group or toast.tag, self._AUMID) + + def remove_toast_group(self, toastGroup: str) -> None: + """ + Removes a group of toast notifications, identified by the specified group ID + """ + toastHistory: ToastNotificationHistory = ToastNotificationManager.history + toastHistory.remove_group(toastGroup, self._AUMID) + class WindowsToaster(BaseWindowsToaster): """ From c3a7af1896731d1091691eb673cd5191cad3abe2 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 00:03:36 +0300 Subject: [PATCH 2/7] tests(remove): add tests for removing toasts --- tests/test_toasts.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_toasts.py b/tests/test_toasts.py index 152165c..5b3120f 100644 --- a/tests/test_toasts.py +++ b/tests/test_toasts.py @@ -296,3 +296,22 @@ def test_system_toast(): newToast.AddAction(dismissBox) InteractableWindowsToaster("Python").show_toast(newToast) + +def test_remove_toast(): + toaster = WindowsToaster("Python") + + newToast = Toast(["Disappearing act"]) + newToast.on_dismissed = lambda _: toaster.remove_toast(newToast) + + toaster.show_toast(newToast) + +def test_remove_toast_group(): + toaster = WindowsToaster("Python") + + toast1 = Toast(["A bigger disappearing act"], group="begone") + toast2 = Toast(["Click me to shazam!"], group="begone") + + toast2.on_activated = lambda _: toaster.remove_toast_group("begone") + + toaster.show_toast(toast1) + toaster.show_toast(toast2) From b7796c2a5a91c153575d7e8fcec88a314b825652 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 00:04:06 +0300 Subject: [PATCH 3/7] docs(remove): provide toast removal example --- docs/advanced_usage.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst index fa4d7bb..060f5c5 100644 --- a/docs/advanced_usage.rst +++ b/docs/advanced_usage.rst @@ -180,6 +180,27 @@ If the :attr:`~windows_toasts.wrappers.ToastSystemButton.relatedInput` is None, .. note:: Ensure the :attr:`~windows_toasts.wrappers.ToastSelection.selection_id` is a positive integer, which represents the interval in minutes. +Removing toasts +--------------- + +You can remove toasts, which will (if on-screen first hide them) and then immediately dismiss them from the action center. + +In the following example, the toast is automatically removed when it is dismissed to the action center: + +.. code-block:: python + + from windows_toasts import WindowsToaster, Toast + + toaster = WindowsToaster("Python") + + newToast = Toast(["Disappearing act"]) + newToast.on_dismissed = lambda _: toaster.remove_toast(newToast) + + toaster.show_toast(newToast) + +.. warning:: + You can only remove toasts that were popped by a toaster with the same AUMID. Additionally, no exception will be thrown if the toast does not exist + ...and much more ---------------- From 6477535307b825576636decaa704437913814391 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 01:19:41 +0300 Subject: [PATCH 4/7] tests(remove): remove the toasts unconditionally Don't wait for on_activated or on_dismissed, otherwise the coverage doesn't run --- tests/conftest.py | 2 +- tests/test_toasts.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9380fd9..759787c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ def real_run_fixture(pytestconfig) -> Iterator[None]: else: with patch("winrt.windows.ui.notifications.ToastNotificationManager.create_toast_notifier"), patch( "winrt.windows.ui.notifications.ToastNotifier.show" - ), patch("winrt.windows.ui.notifications.ToastNotificationHistory.clear"): + ), patch("winrt.windows.ui.notifications.ToastNotificationHistory.clear"), patch("time.sleep"): yield diff --git a/tests/test_toasts.py b/tests/test_toasts.py index 5b3120f..2073538 100644 --- a/tests/test_toasts.py +++ b/tests/test_toasts.py @@ -297,15 +297,24 @@ def test_system_toast(): InteractableWindowsToaster("Python").show_toast(newToast) + def test_remove_toast(): toaster = WindowsToaster("Python") - newToast = Toast(["Disappearing act"]) - newToast.on_dismissed = lambda _: toaster.remove_toast(newToast) toaster.show_toast(newToast) + import time + + time.sleep(0.5) + + toaster.remove_toast(newToast) + + def test_remove_toast_group(): + import time + from src.windows_toasts import ToastActivatedEventArgs + toaster = WindowsToaster("Python") toast1 = Toast(["A bigger disappearing act"], group="begone") @@ -315,3 +324,8 @@ def test_remove_toast_group(): toaster.show_toast(toast1) toaster.show_toast(toast2) + + # If it hasn't been activated, remove it ourselves + time.sleep(1) + + toast2.on_activated(ToastActivatedEventArgs()) From 85898568caf182a89f1dc8846fea29d3e00cb426 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 01:24:03 +0300 Subject: [PATCH 5/7] style(remove): sort imports in test [skip ci] --- tests/test_toasts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_toasts.py b/tests/test_toasts.py index 2073538..939a6a5 100644 --- a/tests/test_toasts.py +++ b/tests/test_toasts.py @@ -313,6 +313,7 @@ def test_remove_toast(): def test_remove_toast_group(): import time + from src.windows_toasts import ToastActivatedEventArgs toaster = WindowsToaster("Python") From 825698cfb03f81087ffae12644c97ce68b02f858 Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 01:33:12 +0300 Subject: [PATCH 6/7] docs(changelog): add entry for toast removal [skip ci] --- CHANGELOG.rst | 5 +++++ src/windows_toasts/toasters.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index abef9f1..5728e3b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +1.2.0 (TBD) +=========== +- Implement removing toasts and toast groups (#145) + - See `'removing toasts' `_ for an example. + 1.1.1 (2024-05-19) ================== - Allow setting attribution text (#140) diff --git a/src/windows_toasts/toasters.py b/src/windows_toasts/toasters.py index fbe4e60..356e0ae 100644 --- a/src/windows_toasts/toasters.py +++ b/src/windows_toasts/toasters.py @@ -64,7 +64,7 @@ def _build_toast_notification(toast: Toast, toastNotification: ToastNotification :rtype: ToastNotificationT """ toastNotification.tag = toast.tag - # Group, a non-empty string, is required for some functionality. If none is provided, use the tag + # Group, a non-empty string, is required for some functionality. If one isn't provided, use the tag toastNotification.group = toast.group or toast.tag if toast.expiration_time is not None: From ff898e0a50d38867d0b8aee04c8715471c2f996b Mon Sep 17 00:00:00 2001 From: DatGuy Date: Tue, 21 May 2024 01:40:55 +0300 Subject: [PATCH 7/7] docs(changelog): correct indentation [skip ci] --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5728e3b..00562f2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ 1.2.0 (TBD) =========== - Implement removing toasts and toast groups (#145) - - See `'removing toasts' `_ for an example. + - See `'removing toasts' `_ for an example. 1.1.1 (2024-05-19) ==================