flet-notifications 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- flet_notifications-0.1.0/PKG-INFO +144 -0
- flet_notifications-0.1.0/README.md +120 -0
- flet_notifications-0.1.0/pyproject.toml +56 -0
- flet_notifications-0.1.0/setup.cfg +4 -0
- flet_notifications-0.1.0/src/flet_notifications/__init__.py +91 -0
- flet_notifications-0.1.0/src/flet_notifications/actions.py +55 -0
- flet_notifications-0.1.0/src/flet_notifications/base.py +87 -0
- flet_notifications-0.1.0/src/flet_notifications/channels.py +87 -0
- flet_notifications-0.1.0/src/flet_notifications/events.py +43 -0
- flet_notifications-0.1.0/src/flet_notifications/local_notifications.py +613 -0
- flet_notifications-0.1.0/src/flet_notifications/options.py +358 -0
- flet_notifications-0.1.0/src/flet_notifications/patcher.py +283 -0
- flet_notifications-0.1.0/src/flet_notifications/scheduling.py +123 -0
- flet_notifications-0.1.0/src/flet_notifications/styles.py +160 -0
- flet_notifications-0.1.0/src/flet_notifications/types.py +53 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/PKG-INFO +144 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/SOURCES.txt +36 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/dependency_links.txt +1 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/entry_points.txt +2 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/requires.txt +1 -0
- flet_notifications-0.1.0/src/flet_notifications.egg-info/top_level.txt +2 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/flet_notifications.dart +3 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/src/background_handler.dart +106 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/src/extension.dart +27 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/src/notifications_service.dart +1386 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/src/parsers.dart +303 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/lib/src/scheduling.dart +106 -0
- flet_notifications-0.1.0/src/flutter/flet_notifications/pubspec.yaml +24 -0
- flet_notifications-0.1.0/tests/test_actions.py +96 -0
- flet_notifications-0.1.0/tests/test_channels.py +250 -0
- flet_notifications-0.1.0/tests/test_events.py +180 -0
- flet_notifications-0.1.0/tests/test_options.py +181 -0
- flet_notifications-0.1.0/tests/test_patcher.py +268 -0
- flet_notifications-0.1.0/tests/test_probes.py +256 -0
- flet_notifications-0.1.0/tests/test_scheduling.py +281 -0
- flet_notifications-0.1.0/tests/test_service.py +177 -0
- flet_notifications-0.1.0/tests/test_styles.py +108 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Onyeka Nwokike
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flet-notifications
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cross-platform local notifications for Flet apps (Android, iOS, macOS, Windows, Linux, Web).
|
|
5
|
+
Author-email: Onyeka Nwokike <nwokikeonyeka@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Nwokike/flet-notifications
|
|
8
|
+
Project-URL: Repository, https://github.com/Nwokike/flet-notifications
|
|
9
|
+
Project-URL: Issues, https://github.com/Nwokike/flet-notifications/issues
|
|
10
|
+
Keywords: flet,notifications,desktop,android,ios,cross-platform
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: flet>=1.0.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# flet-notifications
|
|
26
|
+
|
|
27
|
+
[](https://pypi.python.org/pypi/flet-notifications)
|
|
28
|
+
[](https://pypi.org/project/flet-notifications)
|
|
29
|
+
[](https://github.com/Nwokike/flet-notifications/blob/main/LICENSE)
|
|
30
|
+
|
|
31
|
+
Cross-platform local notifications for [Flet](https://flet.dev) apps.
|
|
32
|
+
|
|
33
|
+
A 1:1 wrapping of the official [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) Flutter plugin, in the same architecture as [flet-ads](https://pypi.org/project/flet-ads/). One `uv add flet-notifications` covers Android, iOS, macOS, Windows, Linux and Web โ no Python dependencies beyond `flet`.
|
|
34
|
+
|
|
35
|
+
## Test it without an account
|
|
36
|
+
|
|
37
|
+
Every release builds the example app for every platform. Sideload any of them and every button is live:
|
|
38
|
+
|
|
39
|
+
| Variant | Download | Notes |
|
|
40
|
+
| :--- | :---: | :--- |
|
|
41
|
+
| ๐ฑ **ARM64** (most phones/TVs) | [**app-arm64-v8a-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-arm64-v8a-release.apk) | Modern 64-bit Android devices โ grab this one unless you know otherwise |
|
|
42
|
+
| ๐ฑ **ARMv7** (older phones/TV boxes) | [**app-armeabi-v7a-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-armeabi-v7a-release.apk) | Legacy 32-bit Android devices |
|
|
43
|
+
| ๐ป **x86_64** (emulators/ChromeOS) | [**app-x86_64-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-x86_64-release.apk) | Android emulators & Chromebooks |
|
|
44
|
+
| ๐ฅ๏ธ **Windows** | [**flet-notifications-example-windows.zip**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-windows.zip) | Portable zip |
|
|
45
|
+
| ๐ **macOS** | [**flet-notifications-example-macos.dmg**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-macos.dmg) | Disk image |
|
|
46
|
+
| ๐ง **Linux** | [**tar.gz**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-linux-x86_64.tar.gz) ยท [**deb**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example_amd64.deb) ยท [**rpm**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example_x86_64.rpm) | Bundle or packages |
|
|
47
|
+
| ๐ **Web** | [**flet-notifications-example-web.tar.gz**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-web.tar.gz) | Static bundle |
|
|
48
|
+
|
|
49
|
+
Plain `flet run` launches the prebuilt client without the extension's Dart half โ notifications need a built app (an artifact above, or `flet build <platform>`).
|
|
50
|
+
|
|
51
|
+
## Platform Support
|
|
52
|
+
|
|
53
|
+
| Platform | Show | Schedule | Actions & callbacks | Channels | Progress | Notes |
|
|
54
|
+
|----------|:----:|:--------:|:-------------------:|:--------:|:--------:|-------|
|
|
55
|
+
| iOS | โ
| โ
| โ
| โ | โ | 13+; attachments, interruption levels |
|
|
56
|
+
| Android | โ
| โ
| โ
| โ
| โ
| 7.0+ (API 24); Android TV works |
|
|
57
|
+
| macOS | โ
| โ
| โ
| โ | โ | 10.15+ |
|
|
58
|
+
| Windows | โ
| one-shot | โ
| โ | โ
| 10+; `cancel`/queries need MSIX identity |
|
|
59
|
+
| Linux | โ
| โ | โ
| โ | โ | D-Bus server; capabilities vary |
|
|
60
|
+
| Web | โ
| โ | Chrome/Edge only | โ | โ | Notifications API; gesture-gated permissions |
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv add flet-notifications
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import flet as ft
|
|
70
|
+
from flet_notifications import LocalNotifications
|
|
71
|
+
|
|
72
|
+
def main(page: ft.Page):
|
|
73
|
+
notifications = LocalNotifications()
|
|
74
|
+
|
|
75
|
+
async def on_tap(e):
|
|
76
|
+
print("tapped:", e.payload)
|
|
77
|
+
|
|
78
|
+
notifications.on_tap = on_tap
|
|
79
|
+
|
|
80
|
+
if await notifications.request_permissions():
|
|
81
|
+
await notifications.show(id=1, title="Hello", body="Hello!")
|
|
82
|
+
|
|
83
|
+
ft.run(main)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Controls
|
|
87
|
+
|
|
88
|
+
| Control | Base | Purpose |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `LocalNotifications` | `ft.Service` | Show/schedule/cancel, permissions, probes, channels; construct once, it registers itself |
|
|
91
|
+
|
|
92
|
+
### Event handlers
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
notifications.on_tap = on_tap # typed: NotificationEvent
|
|
96
|
+
notifications.on_action = on_action # typed: e.action_id, e.input, e.data
|
|
97
|
+
notifications.on_dismissed = handler # typed
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Typed handlers must use `ft.EventHandler[NotificationEvent]`, **not** `ft.ControlEventHandler[NotificationEvent]` โ `ControlEventHandler[X]` double-wraps `X` in `Event[...]`, so `e.payload` raises `AttributeError`. `NotificationEvent` carries `id`, `action_id`, `payload`, `input`, `data` and `response_type`.
|
|
101
|
+
|
|
102
|
+
Taps that arrive while the app wasn't running are replayed on the next start (Android background taps, iOS/macOS cold-start launches).
|
|
103
|
+
|
|
104
|
+
### Configuration
|
|
105
|
+
|
|
106
|
+
Scheduling on Android needs permissions in the app's `pyproject.toml`:
|
|
107
|
+
|
|
108
|
+
```toml
|
|
109
|
+
[tool.flet.android.permission]
|
|
110
|
+
RECEIVE_BOOT_COMPLETED = true # restore alarms after reboot
|
|
111
|
+
SCHEDULE_EXACT_ALARM = true # exact schedule modes
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The scheduled-notification receivers are `<application>` children, which `[tool.flet.android.*]` cannot declare โ the `flet-notifications-patch` console script injects them (plus the core-library-desugaring Gradle flags the plugin needs), idempotently:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uv run flet build apk --yes --project myapp --org com.example -v
|
|
118
|
+
uv run flet-notifications-patch --project-root build/flutter
|
|
119
|
+
cd build/flutter/android && ./gradlew assembleRelease -Psplit-per-abi=true
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Configuration reference
|
|
123
|
+
|
|
124
|
+
**Types:** `NotificationAction`/`NotificationActionInput` (buttons, inline reply) ยท `BigTextStyle`/`BigPictureStyle`/`InboxStyle`/`MessagingStyle` (+ `Person`, `Message`, `AndroidIcon`) ยท `NotificationChannel` (importance, sound, vibration, badge, DND, lights) ยท `NotificationOptions` with `AndroidOptions`/`DarwinOptions`/`LinuxOptions`/`WindowsOptions`/`WebOptions` (the full per-platform detail surface) ยท enums `RepeatInterval`, `DateTimeComponents`, `AndroidScheduleMode`, `Importance`, `GroupAlertBehavior`, `InterruptionLevel`.
|
|
125
|
+
|
|
126
|
+
**Calls:** `show` ยท `schedule` ยท `periodically_show` ยท `cancel`/`cancel_all`/`cancel_pending` ยท `request_permissions` ยท `are_notifications_enabled` ยท `can_schedule_exact_notifications`/`request_exact_alarm_permission` ยท `request_full_screen_intent_permission` ยท `has`/`request_notification_policy_access` ยท `open_app_notification_settings` ยท `get_pending_notifications`/`get_active_notifications`/`get_notification_app_launch_details` ยท `create`/`delete`/`get_notification_channels` (+ groups) ยท `start`/`stop_foreground_service`.
|
|
127
|
+
|
|
128
|
+
**Errors:** `NotificationError` with a stable `code` โ `unsupported_platform`, `init_failed`, `show_failed`, `schedule_failed`, `invalid_action`, `invalid_schedule`, `invalid_style`, `invalid_channel`, `invalid_option`, `invalid_service_id`, `channel_failed`, `foreground_service_failed`, `query_failed`, `permission_failed`, `unknown_method`.
|
|
129
|
+
|
|
130
|
+
## Limitations
|
|
131
|
+
|
|
132
|
+
- Linux/Web have no OS scheduler; Windows schedules one-shot only.
|
|
133
|
+
- Android channel sound/vibration are immutable after creation; aggressive OEM skins may drop alarms after force-stop; Samsung caps ~500 alarms.
|
|
134
|
+
- Interruptions beyond `passive` on Apple need Xcode capabilities.
|
|
135
|
+
- Not wrapped (upstream niche): HTML-format style flags, `MediaStyle`, action `semanticAction`/`invisible`/`contextual`, CarPlay, message data MIME/URI, Linux custom hints.
|
|
136
|
+
- iOS IPAs need Apple signing and are built owner-side; CI builds every other platform.
|
|
137
|
+
|
|
138
|
+
## Example
|
|
139
|
+
|
|
140
|
+
`examples/flet_notifications_example` โ every API on one screen with a timestamped event log, built for Windows, macOS, Linux, Android and Web on every release.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# flet-notifications
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/flet-notifications)
|
|
4
|
+
[](https://pypi.org/project/flet-notifications)
|
|
5
|
+
[](https://github.com/Nwokike/flet-notifications/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Cross-platform local notifications for [Flet](https://flet.dev) apps.
|
|
8
|
+
|
|
9
|
+
A 1:1 wrapping of the official [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) Flutter plugin, in the same architecture as [flet-ads](https://pypi.org/project/flet-ads/). One `uv add flet-notifications` covers Android, iOS, macOS, Windows, Linux and Web โ no Python dependencies beyond `flet`.
|
|
10
|
+
|
|
11
|
+
## Test it without an account
|
|
12
|
+
|
|
13
|
+
Every release builds the example app for every platform. Sideload any of them and every button is live:
|
|
14
|
+
|
|
15
|
+
| Variant | Download | Notes |
|
|
16
|
+
| :--- | :---: | :--- |
|
|
17
|
+
| ๐ฑ **ARM64** (most phones/TVs) | [**app-arm64-v8a-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-arm64-v8a-release.apk) | Modern 64-bit Android devices โ grab this one unless you know otherwise |
|
|
18
|
+
| ๐ฑ **ARMv7** (older phones/TV boxes) | [**app-armeabi-v7a-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-armeabi-v7a-release.apk) | Legacy 32-bit Android devices |
|
|
19
|
+
| ๐ป **x86_64** (emulators/ChromeOS) | [**app-x86_64-release.apk**](https://github.com/Nwokike/flet-notifications/releases/latest/download/app-x86_64-release.apk) | Android emulators & Chromebooks |
|
|
20
|
+
| ๐ฅ๏ธ **Windows** | [**flet-notifications-example-windows.zip**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-windows.zip) | Portable zip |
|
|
21
|
+
| ๐ **macOS** | [**flet-notifications-example-macos.dmg**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-macos.dmg) | Disk image |
|
|
22
|
+
| ๐ง **Linux** | [**tar.gz**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-linux-x86_64.tar.gz) ยท [**deb**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example_amd64.deb) ยท [**rpm**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example_x86_64.rpm) | Bundle or packages |
|
|
23
|
+
| ๐ **Web** | [**flet-notifications-example-web.tar.gz**](https://github.com/Nwokike/flet-notifications/releases/latest/download/flet-notifications-example-web.tar.gz) | Static bundle |
|
|
24
|
+
|
|
25
|
+
Plain `flet run` launches the prebuilt client without the extension's Dart half โ notifications need a built app (an artifact above, or `flet build <platform>`).
|
|
26
|
+
|
|
27
|
+
## Platform Support
|
|
28
|
+
|
|
29
|
+
| Platform | Show | Schedule | Actions & callbacks | Channels | Progress | Notes |
|
|
30
|
+
|----------|:----:|:--------:|:-------------------:|:--------:|:--------:|-------|
|
|
31
|
+
| iOS | โ
| โ
| โ
| โ | โ | 13+; attachments, interruption levels |
|
|
32
|
+
| Android | โ
| โ
| โ
| โ
| โ
| 7.0+ (API 24); Android TV works |
|
|
33
|
+
| macOS | โ
| โ
| โ
| โ | โ | 10.15+ |
|
|
34
|
+
| Windows | โ
| one-shot | โ
| โ | โ
| 10+; `cancel`/queries need MSIX identity |
|
|
35
|
+
| Linux | โ
| โ | โ
| โ | โ | D-Bus server; capabilities vary |
|
|
36
|
+
| Web | โ
| โ | Chrome/Edge only | โ | โ | Notifications API; gesture-gated permissions |
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv add flet-notifications
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import flet as ft
|
|
46
|
+
from flet_notifications import LocalNotifications
|
|
47
|
+
|
|
48
|
+
def main(page: ft.Page):
|
|
49
|
+
notifications = LocalNotifications()
|
|
50
|
+
|
|
51
|
+
async def on_tap(e):
|
|
52
|
+
print("tapped:", e.payload)
|
|
53
|
+
|
|
54
|
+
notifications.on_tap = on_tap
|
|
55
|
+
|
|
56
|
+
if await notifications.request_permissions():
|
|
57
|
+
await notifications.show(id=1, title="Hello", body="Hello!")
|
|
58
|
+
|
|
59
|
+
ft.run(main)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Controls
|
|
63
|
+
|
|
64
|
+
| Control | Base | Purpose |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `LocalNotifications` | `ft.Service` | Show/schedule/cancel, permissions, probes, channels; construct once, it registers itself |
|
|
67
|
+
|
|
68
|
+
### Event handlers
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
notifications.on_tap = on_tap # typed: NotificationEvent
|
|
72
|
+
notifications.on_action = on_action # typed: e.action_id, e.input, e.data
|
|
73
|
+
notifications.on_dismissed = handler # typed
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Typed handlers must use `ft.EventHandler[NotificationEvent]`, **not** `ft.ControlEventHandler[NotificationEvent]` โ `ControlEventHandler[X]` double-wraps `X` in `Event[...]`, so `e.payload` raises `AttributeError`. `NotificationEvent` carries `id`, `action_id`, `payload`, `input`, `data` and `response_type`.
|
|
77
|
+
|
|
78
|
+
Taps that arrive while the app wasn't running are replayed on the next start (Android background taps, iOS/macOS cold-start launches).
|
|
79
|
+
|
|
80
|
+
### Configuration
|
|
81
|
+
|
|
82
|
+
Scheduling on Android needs permissions in the app's `pyproject.toml`:
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
[tool.flet.android.permission]
|
|
86
|
+
RECEIVE_BOOT_COMPLETED = true # restore alarms after reboot
|
|
87
|
+
SCHEDULE_EXACT_ALARM = true # exact schedule modes
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The scheduled-notification receivers are `<application>` children, which `[tool.flet.android.*]` cannot declare โ the `flet-notifications-patch` console script injects them (plus the core-library-desugaring Gradle flags the plugin needs), idempotently:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv run flet build apk --yes --project myapp --org com.example -v
|
|
94
|
+
uv run flet-notifications-patch --project-root build/flutter
|
|
95
|
+
cd build/flutter/android && ./gradlew assembleRelease -Psplit-per-abi=true
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration reference
|
|
99
|
+
|
|
100
|
+
**Types:** `NotificationAction`/`NotificationActionInput` (buttons, inline reply) ยท `BigTextStyle`/`BigPictureStyle`/`InboxStyle`/`MessagingStyle` (+ `Person`, `Message`, `AndroidIcon`) ยท `NotificationChannel` (importance, sound, vibration, badge, DND, lights) ยท `NotificationOptions` with `AndroidOptions`/`DarwinOptions`/`LinuxOptions`/`WindowsOptions`/`WebOptions` (the full per-platform detail surface) ยท enums `RepeatInterval`, `DateTimeComponents`, `AndroidScheduleMode`, `Importance`, `GroupAlertBehavior`, `InterruptionLevel`.
|
|
101
|
+
|
|
102
|
+
**Calls:** `show` ยท `schedule` ยท `periodically_show` ยท `cancel`/`cancel_all`/`cancel_pending` ยท `request_permissions` ยท `are_notifications_enabled` ยท `can_schedule_exact_notifications`/`request_exact_alarm_permission` ยท `request_full_screen_intent_permission` ยท `has`/`request_notification_policy_access` ยท `open_app_notification_settings` ยท `get_pending_notifications`/`get_active_notifications`/`get_notification_app_launch_details` ยท `create`/`delete`/`get_notification_channels` (+ groups) ยท `start`/`stop_foreground_service`.
|
|
103
|
+
|
|
104
|
+
**Errors:** `NotificationError` with a stable `code` โ `unsupported_platform`, `init_failed`, `show_failed`, `schedule_failed`, `invalid_action`, `invalid_schedule`, `invalid_style`, `invalid_channel`, `invalid_option`, `invalid_service_id`, `channel_failed`, `foreground_service_failed`, `query_failed`, `permission_failed`, `unknown_method`.
|
|
105
|
+
|
|
106
|
+
## Limitations
|
|
107
|
+
|
|
108
|
+
- Linux/Web have no OS scheduler; Windows schedules one-shot only.
|
|
109
|
+
- Android channel sound/vibration are immutable after creation; aggressive OEM skins may drop alarms after force-stop; Samsung caps ~500 alarms.
|
|
110
|
+
- Interruptions beyond `passive` on Apple need Xcode capabilities.
|
|
111
|
+
- Not wrapped (upstream niche): HTML-format style flags, `MediaStyle`, action `semanticAction`/`invisible`/`contextual`, CarPlay, message data MIME/URI, Linux custom hints.
|
|
112
|
+
- iOS IPAs need Apple signing and are built owner-side; CI builds every other platform.
|
|
113
|
+
|
|
114
|
+
## Example
|
|
115
|
+
|
|
116
|
+
`examples/flet_notifications_example` โ every API on one screen with a timestamped event log, built for Windows, macOS, Linux, Android and Web on every release.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flet-notifications"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Cross-platform local notifications for Flet apps (Android, iOS, macOS, Windows, Linux, Web)."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Onyeka Nwokike", email = "nwokikeonyeka@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
keywords = ["flet", "notifications", "desktop", "android", "ios", "cross-platform"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"flet>=1.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/Nwokike/flet-notifications"
|
|
28
|
+
Repository = "https://github.com/Nwokike/flet-notifications"
|
|
29
|
+
Issues = "https://github.com/Nwokike/flet-notifications/issues"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
flet-notifications-patch = "flet_notifications.patcher:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
"flutter.flet_notifications" = ["pubspec.yaml", "lib/**"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
license-files = ["LICENSE"]
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["setuptools"]
|
|
42
|
+
build-backend = "setuptools.build_meta"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"pytest-asyncio>=0.24",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
asyncio_mode = "auto"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
# Match the flet core source style: Optional[...] and quoted forward
|
|
55
|
+
# references in control/event annotations.
|
|
56
|
+
ignore = ["UP045", "UP037"]
|
|
@@ -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
|