flet-notifications 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ """flet-notifications: cross-platform local notifications for Flet apps.
2
+
3
+ Wraps the battle-tested ``flutter_local_notifications`` Flutter plugin and
4
+ exposes it as a native Flet service (``ft.Service``), following the same
5
+ extension architecture as ``flet-ads`` / ``flet-yandex-ads``.
6
+ """
7
+
8
+ from flet_notifications.actions import NotificationAction, NotificationActionInput
9
+ from flet_notifications.base import (
10
+ NotificationError,
11
+ NotificationErrorCode,
12
+ NotificationEvent,
13
+ ResponseType,
14
+ )
15
+ from flet_notifications.channels import NotificationChannel
16
+ from flet_notifications.events import (
17
+ ActiveNotification,
18
+ LaunchDetails,
19
+ PendingNotification,
20
+ )
21
+ from flet_notifications.local_notifications import LocalNotifications
22
+ from flet_notifications.options import (
23
+ AndroidOptions,
24
+ DarwinOptions,
25
+ InterruptionLevel,
26
+ LinuxOptions,
27
+ LinuxUrgency,
28
+ NotificationOptions,
29
+ Priority,
30
+ Visibility,
31
+ WebOptions,
32
+ WindowsOptions,
33
+ WindowsScenario,
34
+ )
35
+ from flet_notifications.styles import (
36
+ AndroidIcon,
37
+ BigPictureStyle,
38
+ BigTextStyle,
39
+ InboxStyle,
40
+ Message,
41
+ MessagingStyle,
42
+ Person,
43
+ Style,
44
+ )
45
+ from flet_notifications.types import (
46
+ AndroidScheduleMode,
47
+ DateTimeComponents,
48
+ GroupAlertBehavior,
49
+ Importance,
50
+ RepeatInterval,
51
+ )
52
+
53
+ __version__ = "0.1.0"
54
+
55
+ __all__ = [
56
+ "ActiveNotification",
57
+ "AndroidIcon",
58
+ "AndroidOptions",
59
+ "AndroidScheduleMode",
60
+ "BigPictureStyle",
61
+ "BigTextStyle",
62
+ "DarwinOptions",
63
+ "DateTimeComponents",
64
+ "GroupAlertBehavior",
65
+ "Importance",
66
+ "InboxStyle",
67
+ "InterruptionLevel",
68
+ "LaunchDetails",
69
+ "LinuxOptions",
70
+ "LinuxUrgency",
71
+ "LocalNotifications",
72
+ "Message",
73
+ "MessagingStyle",
74
+ "NotificationAction",
75
+ "NotificationActionInput",
76
+ "NotificationChannel",
77
+ "NotificationError",
78
+ "NotificationErrorCode",
79
+ "NotificationEvent",
80
+ "NotificationOptions",
81
+ "PendingNotification",
82
+ "Person",
83
+ "Priority",
84
+ "RepeatInterval",
85
+ "ResponseType",
86
+ "Style",
87
+ "Visibility",
88
+ "WebOptions",
89
+ "WindowsOptions",
90
+ "WindowsScenario",
91
+ ]
@@ -0,0 +1,55 @@
1
+ """Action buttons and inline-reply inputs for notifications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional
7
+
8
+
9
+ @dataclass(kw_only=True)
10
+ class NotificationActionInput:
11
+ """Inline-reply input attached to a notification action.
12
+
13
+ Maps to ``AndroidNotificationActionInput`` on Android, the text variant of
14
+ ``DarwinNotificationAction`` on iOS/macOS and ``WindowsInput`` on Windows.
15
+ """
16
+
17
+ label: str = "" # placeholder / button title over the text field
18
+ choices: list[str] = field(default_factory=list) # quick-reply suggestions
19
+ allow_free_form_input: bool = True
20
+ allowed_mime_types: list[str] = field(default_factory=list)
21
+
22
+ def to_dict(self) -> dict:
23
+ return {
24
+ "label": self.label,
25
+ "choices": list(self.choices),
26
+ "allow_free_form_input": self.allow_free_form_input,
27
+ "allowed_mime_types": list(self.allowed_mime_types),
28
+ }
29
+
30
+
31
+ @dataclass(kw_only=True)
32
+ class NotificationAction:
33
+ """A button shown on a notification.
34
+
35
+ When the button is pressed, ``on_action`` fires with ``action_id == id``.
36
+ If ``input`` is set, the OS shows an inline-reply field and the typed text
37
+ arrives as ``NotificationEvent.input``.
38
+ """
39
+
40
+ id: str
41
+ title: str
42
+ destructive: bool = False # iOS/macOS: render as destructive
43
+ foreground: bool = True # bring the app to the foreground on press
44
+ cancel_notification: bool = True # Android: dismiss the notification
45
+ input: Optional[NotificationActionInput] = None
46
+
47
+ def to_dict(self) -> dict:
48
+ return {
49
+ "id": self.id,
50
+ "title": self.title,
51
+ "destructive": self.destructive,
52
+ "foreground": self.foreground,
53
+ "cancel_notification": self.cancel_notification,
54
+ "input": self.input.to_dict() if self.input else None,
55
+ }
@@ -0,0 +1,87 @@
1
+ """Shared types and helpers for flet-notifications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+
8
+ import flet as ft
9
+
10
+
11
+ class ResponseType(str, Enum):
12
+ """Type of a notification response delivered from the OS to the app."""
13
+
14
+ TAP = "tap"
15
+ ACTION = "action"
16
+ DISMISSED = "dismissed"
17
+
18
+
19
+ @dataclass
20
+ class NotificationEvent(ft.Event[ft.EventControlType]):
21
+ """A notification tap, action-button press or dismissal.
22
+
23
+ Delivered to ``on_tap`` / ``on_action`` / ``on_dismissed`` handlers.
24
+ """
25
+
26
+ id: int = 0
27
+ action_id: str = "" # "" for a plain tap; the button id for actions
28
+ payload: str = ""
29
+ input: str = "" # inline-reply text, when the action had an input
30
+ response_type: str = ResponseType.TAP.value
31
+ # Windows action inputs arrive in the inherited `data` field, keyed by
32
+ # the input id (Windows leaves `input` null).
33
+
34
+
35
+ class NotificationErrorCode:
36
+ """Stable machine-readable error codes raised by the service."""
37
+
38
+ UNSUPPORTED_PLATFORM = "unsupported_platform"
39
+ INIT_FAILED = "init_failed"
40
+ SHOW_FAILED = "show_failed"
41
+ SCHEDULE_FAILED = "schedule_failed"
42
+ INVALID_ACTION = "invalid_action"
43
+ INVALID_SCHEDULE = "invalid_schedule"
44
+ INVALID_STYLE = "invalid_style"
45
+ INVALID_CHANNEL = "invalid_channel"
46
+ INVALID_SERVICE_ID = "invalid_service_id"
47
+ INVALID_OPTION = "invalid_option"
48
+ CHANNEL_FAILED = "channel_failed"
49
+ FOREGROUND_SERVICE_FAILED = "foreground_service_failed"
50
+ QUERY_FAILED = "query_failed"
51
+ PERMISSION_FAILED = "permission_failed"
52
+ UNKNOWN_METHOD = "unknown_method"
53
+
54
+
55
+ class NotificationError(Exception):
56
+ """Raised when a notification operation fails.
57
+
58
+ ``code`` is a stable machine-readable identifier — see
59
+ :class:`NotificationErrorCode` for the full list.
60
+ """
61
+
62
+ def __init__(self, code: str, message: str = "") -> None:
63
+ self.code = code
64
+ self.message = message
65
+ super().__init__(f"{code}: {message}" if message else code)
66
+
67
+ def __repr__(self) -> str:
68
+ return f"NotificationError(code={self.code!r}, message={self.message!r})"
69
+
70
+
71
+ def check_result(result: object) -> None:
72
+ """Raise :class:`NotificationError` if a Dart invoke returned an error string.
73
+
74
+ Dart service methods return ``"error:<code>:<message>"`` on failure.
75
+ """
76
+ if isinstance(result, str) and result.startswith("error:"):
77
+ _, _, rest = result.partition(":")
78
+ code, _, message = rest.partition(":")
79
+ raise NotificationError(code.strip(), message.strip())
80
+
81
+
82
+ def to_bool(result: object) -> bool:
83
+ """Coerce a Dart invoke result to bool (``"true"``/``"false"``)."""
84
+ check_result(result)
85
+ if isinstance(result, bool):
86
+ return result
87
+ return str(result).strip().lower() == "true"
@@ -0,0 +1,87 @@
1
+ """Android notification channels."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from flet_notifications.types import Importance
9
+
10
+
11
+ @dataclass(kw_only=True)
12
+ class NotificationChannel:
13
+ """An Android notification channel (required since Android 8.0).
14
+
15
+ Sound and vibration are fixed at channel creation; to change them you
16
+ create a new channel with a new id.
17
+ """
18
+
19
+ id: str
20
+ name: str
21
+ description: str = ""
22
+ group_id: Optional[str] = None
23
+ importance: Importance = Importance.DEFAULT
24
+ bypass_dnd: bool = False
25
+ play_sound: bool = True
26
+ sound: Optional[str] = None # raw resource name, e.g. "alert"
27
+ enable_vibration: bool = True
28
+ vibration_pattern: Optional[list[int]] = None
29
+ show_badge: bool = True
30
+ enable_lights: bool = False
31
+ led_color: Optional[str] = None # #RRGGBB
32
+
33
+ def __post_init__(self) -> None:
34
+ if not self.id:
35
+ raise ValueError("channel id is required")
36
+ if not self.name:
37
+ raise ValueError("channel name is required")
38
+ if self.led_color is not None:
39
+ self.led_color = _validate_hex(self.led_color)
40
+
41
+ def to_dict(self) -> dict:
42
+ return {
43
+ "id": self.id,
44
+ "name": self.name,
45
+ "description": self.description,
46
+ "group_id": self.group_id,
47
+ "importance": self.importance.value,
48
+ "bypass_dnd": self.bypass_dnd,
49
+ "play_sound": self.play_sound,
50
+ "sound": self.sound,
51
+ "enable_vibration": self.enable_vibration,
52
+ "vibration_pattern": (
53
+ list(self.vibration_pattern) if self.vibration_pattern else None
54
+ ),
55
+ "show_badge": self.show_badge,
56
+ "enable_lights": self.enable_lights,
57
+ "led_color": self.led_color,
58
+ }
59
+
60
+ @classmethod
61
+ def from_dict(cls, data: dict) -> "NotificationChannel":
62
+ return cls(
63
+ id=str(data.get("id", "")),
64
+ name=str(data.get("name", "")),
65
+ description=str(data.get("description") or ""),
66
+ group_id=data.get("groupId") or data.get("group_id"),
67
+ importance=Importance(data.get("importance", "defaultImportance")),
68
+ bypass_dnd=bool(data.get("bypass_dnd", False)),
69
+ play_sound=bool(data.get("play_sound", True)),
70
+ sound=data.get("sound"),
71
+ enable_vibration=bool(data.get("enable_vibration", True)),
72
+ vibration_pattern=data.get("vibration_pattern"),
73
+ show_badge=bool(data.get("show_badge", True)),
74
+ enable_lights=bool(data.get("enable_lights", False)),
75
+ led_color=data.get("led_color"),
76
+ )
77
+
78
+
79
+ def _validate_hex(value: str) -> str:
80
+ v = value.strip()
81
+ if not v.startswith("#") or len(v) not in (7, 9):
82
+ raise ValueError(f"invalid hex color: {value!r}")
83
+ try:
84
+ int(v[1:], 16)
85
+ except ValueError as e:
86
+ raise ValueError(f"invalid hex color: {value!r}") from e
87
+ return v
@@ -0,0 +1,43 @@
1
+ """Event payload types delivered from the Dart side."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from flet_notifications.base import NotificationEvent
9
+
10
+
11
+ @dataclass
12
+ class PendingNotification:
13
+ """A scheduled notification that has not been delivered yet.
14
+
15
+ Returned by :meth:`LocalNotifications.get_pending_notifications`.
16
+ """
17
+
18
+ id: int
19
+ title: str = ""
20
+ body: str = ""
21
+ payload: str = ""
22
+
23
+
24
+ @dataclass
25
+ class ActiveNotification:
26
+ """A notification currently shown in the notification tray."""
27
+
28
+ id: int = 0
29
+ channel_id: str = ""
30
+ group_key: str = ""
31
+ tag: str = ""
32
+ title: str = ""
33
+ body: str = ""
34
+ payload: str = ""
35
+ big_text: str = ""
36
+
37
+
38
+ @dataclass
39
+ class LaunchDetails:
40
+ """Why/how the app was launched, if a notification launched it."""
41
+
42
+ did_notification_launch_app: bool = False
43
+ notification: Optional[NotificationEvent] = None