Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support removing toasts and toast groups #145

Merged
merged 7 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.2.0 (TBD)
===========
- Implement removing toasts and toast groups (#145)
- See `'removing toasts' <https://windows-toasts.readthedocs.io/en/latest/advanced_usage.html#removing-toasts>`_ for an example.

1.1.1 (2024-05-19)
==================
- Allow setting attribution text (#140)
Expand Down
21 changes: 21 additions & 0 deletions docs/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------

Expand Down
4 changes: 2 additions & 2 deletions src/windows_toasts/toast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down
19 changes: 17 additions & 2 deletions src/windows_toasts/toasters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 one isn't provided, use the tag
toastNotification.group = toast.group or toast.tag

if toast.expiration_time is not None:
toastNotification.expiration_time = toast.expiration_time
Expand Down Expand Up @@ -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):
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
34 changes: 34 additions & 0 deletions tests/test_toasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,37 @@ def test_system_toast():
newToast.AddAction(dismissBox)

InteractableWindowsToaster("Python").show_toast(newToast)


def test_remove_toast():
toaster = WindowsToaster("Python")
newToast = Toast(["Disappearing act"])

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")
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)

# If it hasn't been activated, remove it ourselves
time.sleep(1)

toast2.on_activated(ToastActivatedEventArgs())