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.
- flet_notifications/__init__.py +91 -0
- flet_notifications/actions.py +55 -0
- flet_notifications/base.py +87 -0
- flet_notifications/channels.py +87 -0
- flet_notifications/events.py +43 -0
- flet_notifications/local_notifications.py +613 -0
- flet_notifications/options.py +358 -0
- flet_notifications/patcher.py +283 -0
- flet_notifications/scheduling.py +123 -0
- flet_notifications/styles.py +160 -0
- flet_notifications/types.py +53 -0
- flet_notifications-0.1.0.dist-info/METADATA +144 -0
- flet_notifications-0.1.0.dist-info/RECORD +24 -0
- flet_notifications-0.1.0.dist-info/WHEEL +5 -0
- flet_notifications-0.1.0.dist-info/entry_points.txt +2 -0
- flet_notifications-0.1.0.dist-info/licenses/LICENSE +21 -0
- flet_notifications-0.1.0.dist-info/top_level.txt +2 -0
- flutter/flet_notifications/lib/flet_notifications.dart +3 -0
- flutter/flet_notifications/lib/src/background_handler.dart +106 -0
- flutter/flet_notifications/lib/src/extension.dart +27 -0
- flutter/flet_notifications/lib/src/notifications_service.dart +1386 -0
- flutter/flet_notifications/lib/src/parsers.dart +303 -0
- flutter/flet_notifications/lib/src/scheduling.dart +106 -0
- flutter/flet_notifications/pubspec.yaml +24 -0
|
@@ -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
|