├── .python-version ├── src ├── flutter │ └── flet_onesignal │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── lib │ │ ├── flet_onesignal.dart │ │ └── src │ │ │ ├── utils │ │ │ └── funcs.dart │ │ │ ├── create_control.dart │ │ │ └── flet_onesignal.dart │ │ ├── .gitignore │ │ ├── LICENSE │ │ └── pubspec.yaml └── flet_onesignal │ ├── __init__.py │ ├── languages.py │ └── flet_onesignal.py ├── examples └── flet_onesignal_example │ ├── pyproject.toml │ └── src │ └── main.py ├── LICENSE ├── pyproject.toml ├── .gitignore └── README.md /.python-version: -------------------------------------------------------------------------------- 1 | 3.12.4 2 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /src/flet_onesignal/__init__.py: -------------------------------------------------------------------------------- 1 | from flet_onesignal.flet_onesignal import OneSignal, OneSignalSettings -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/README.md: -------------------------------------------------------------------------------- 1 | # Flet OneSignal 2 | 3 | --- 4 | 5 | ### Flutter OneSignal package integration for Python Flet 6 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/lib/flet_onesignal.dart: -------------------------------------------------------------------------------- 1 | library flet_onesignal; 2 | 3 | export '../src/create_control.dart' show createControl, ensureInitialized; -------------------------------------------------------------------------------- /examples/flet_onesignal_example/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "flet-onesignal-example" 3 | version = "0.1.0" 4 | description = "flet-onesignal-example" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | authors = [ 8 | { name = "developer", email = "you@example.com" } 9 | ] 10 | 11 | dependencies = [ 12 | "flet>=0.26.0", 13 | "flet-onesignal>=0.2.0", 14 | ] 15 | 16 | [tool.uv] 17 | dev-dependencies = [ 18 | "flet[all]>=0.26.0", 19 | ] 20 | 21 | [tool.poetry.group.dev.dependencies] 22 | flet = {extras = ["all"], version = "0.26.0"} 23 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/lib/src/utils/funcs.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | 4 | 5 | void handleFlutterError({ 6 | required String method, 7 | required Object error, 8 | required StackTrace stackTrace, 9 | required void Function(String, String, String) trigger, 10 | required String controlId, 11 | }) { 12 | debugPrint("❌ Error in $method:\n$error\n$stackTrace"); 13 | 14 | trigger( 15 | controlId, 16 | "error", 17 | jsonEncode({ 18 | "method": method, 19 | "message": error.toString(), 20 | "stackTrace": stackTrace.toString(), 21 | }), 22 | ); 23 | } -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/lib/src/create_control.dart: -------------------------------------------------------------------------------- 1 | import 'package:flet/flet.dart'; 2 | import 'flet_onesignal.dart'; 3 | 4 | 5 | CreateControlFactory createControl = (CreateControlArgs args) { 6 | switch (args.control.type) { 7 | case 'flet_onesignal': 8 | if (args.parent == null) { 9 | throw ArgumentError('Parent cannot be null'); 10 | } 11 | return FletOneSignalControl( 12 | parent: args.parent!, // Força a não nulidade após a verificação 13 | control: args.control, 14 | backend: args.backend 15 | ); 16 | default: 17 | return null; 18 | } 19 | }; 20 | 21 | void ensureInitialized() { 22 | // Required initializations, if any 23 | // Se houver inicializações necessárias 24 | } 25 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | 33 | # FVM Version Cache 34 | .fvm/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bruno Brown 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. 22 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bruno Brown 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. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "flet-onesignal" 3 | version = "0.3.3" 4 | description = "Flutter OneSignal package integration for Python Flet." 5 | requires-python = ">=3.9" 6 | readme = "README.md" 7 | authors = [ 8 | { name = "brunobrown", email = "brunobrown.86@gmail.com" } 9 | ] 10 | license = "MIT" 11 | license-files = ["LICENSE"] 12 | 13 | dependencies = [ 14 | "flet>=0.26.0", 15 | ] 16 | 17 | classifiers = [ 18 | "Topic :: Software Development :: Debuggers", 19 | "Intended Audience :: Developers", 20 | "Natural Language :: English", 21 | "Natural Language :: Portuguese (Brazilian)", 22 | "Operating System :: OS Independent", 23 | "Development Status :: 5 - Production/Stable", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | "Programming Language :: Python :: 3.12", 28 | "Programming Language :: Python :: 3.13", 29 | ] 30 | 31 | [project.urls] 32 | Homepage = "https://github.com/brunobrown/flet-onesignal" 33 | #Documentation = "" 34 | Repository = "https://github.com/brunobrown/flet-onesignal" 35 | Issues = "https://github.com/brunobrown/flet-onesignal/issues" 36 | #Changelog = "" 37 | 38 | [tool.uv] 39 | dev-dependencies = [ 40 | "flet[all]>=0.26.0", 41 | ] 42 | 43 | #[tool.setuptools] 44 | ##packages = ["flet_onesignal"] 45 | ##package-dir = {"" = "src"} 46 | 47 | [tool.setuptools.package-data] 48 | "flutter.flet_onesignal" = ["**/*"] 49 | 50 | [build-system] 51 | requires = ["setuptools"] 52 | build-backend = "setuptools.build_meta" 53 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flet_onesignal 2 | description: "Flutter OneSignal package integration for Python Flet." 3 | version: 0.0.2 4 | #homepage: https://github.com/brunobrown/onesignal-flet 5 | repository: https://github.com/brunobrown/flet-onesignal 6 | 7 | environment: 8 | sdk: ^3.5.2 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | flet: ^0.26.0 16 | onesignal_flutter: ^5.3.2 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | flutter_lints: ^4.0.0 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter packages. 27 | flutter: 28 | 29 | # To add assets to your package, add an assets section, like this: 30 | # assets: 31 | # - images/a_dot_burr.jpeg 32 | # - images/a_dot_ham.jpeg 33 | # 34 | # For details regarding assets in packages, see 35 | # https://flutter.dev/to/asset-from-package 36 | # 37 | # An image asset can refer to one or more resolution-specific "variants", see 38 | # https://flutter.dev/to/resolution-aware-images 39 | 40 | # To add custom fonts to your package, add a fonts section here, 41 | # in this "flutter" section. Each entry in this list should have a 42 | # "family" key with the font family name, and a "fonts" key with a 43 | # list giving the asset and other descriptors for the font. For 44 | # example: 45 | # fonts: 46 | # - family: Schyler 47 | # fonts: 48 | # - asset: fonts/Schyler-Regular.ttf 49 | # - asset: fonts/Schyler-Italic.ttf 50 | # style: italic 51 | # - family: Trajan Pro 52 | # fonts: 53 | # - asset: fonts/TrajanPro.ttf 54 | # - asset: fonts/TrajanPro_Bold.ttf 55 | # weight: 700 56 | # 57 | # For details regarding fonts in packages, see 58 | # https://flutter.dev/to/font-from-package 59 | -------------------------------------------------------------------------------- /src/flet_onesignal/languages.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class Language(Enum): 4 | """ 5 | Enumeração de línguas suportadas pelo OneSignal. 6 | """ 7 | 8 | ENGLISH = "en" 9 | SPANISH = "es" 10 | PORTUGUESE = "pt" 11 | FRENCH = "fr" 12 | GERMAN = "de" 13 | ITALIAN = "it" 14 | RUSSIAN = "ru" 15 | CHINESE_SIMPLIFIED = "zh-Hans" 16 | CHINESE_TRADITIONAL = "zh-Hant" 17 | JAPANESE = "ja" 18 | KOREAN = "ko" 19 | ARABIC = "ar" 20 | HINDI = "hi" 21 | TURKISH = "tr" 22 | DUTCH = "nl" 23 | SWEDISH = "sv" 24 | FINNISH = "fi" 25 | DANISH = "da" 26 | NORWEGIAN = "no" 27 | POLISH = "pl" 28 | CZECH = "cs" 29 | GREEK = "el" 30 | HUNGARIAN = "hu" 31 | ROMANIAN = "ro" 32 | THAI = "th" 33 | VIETNAMESE = "vi" 34 | INDONESIAN = "id" 35 | MALAY = "ms" 36 | FILIPINO = "tl" 37 | SWAHILI = "sw" 38 | URDU = "ur" 39 | PERSIAN = "fa" 40 | BENGALI = "bn" 41 | TAMIL = "ta" 42 | TELUGU = "te" 43 | MARATHI = "mr" 44 | GUJARATI = "gu" 45 | KANNADA = "kn" 46 | MALAYALAM = "ml" 47 | PUNJABI = "pa" 48 | SINHALA = "si" 49 | NEPALI = "ne" 50 | BURMESE = "my" 51 | KHMER = "km" 52 | LAO = "lo" 53 | MONGOLIAN = "mn" 54 | TIBETAN = "bo" 55 | UZBEK = "uz" 56 | KAZAKH = "kk" 57 | AZERBAIJANI = "az" 58 | GEORGIAN = "ka" 59 | ARMENIAN = "hy" 60 | HEBREW = "he" 61 | YIDDISH = "yi" 62 | ESPERANTO = "eo" 63 | LATIN = "la" 64 | BASQUE = "eu" 65 | CATALAN = "ca" 66 | GALICIAN = "gl" 67 | WELSH = "cy" 68 | IRISH = "ga" 69 | SCOTTISH_GAELIC = "gd" 70 | BRETON = "br" 71 | CORSICAN = "co" 72 | ALBANIAN = "sq" 73 | BOSNIAN = "bs" 74 | CROATIAN = "hr" 75 | SERBIAN = "sr" 76 | MACEDONIAN = "mk" 77 | BULGARIAN = "bg" 78 | SLOVAK = "sk" 79 | SLOVENIAN = "sl" 80 | ESTONIAN = "et" 81 | LATVIAN = "lv" 82 | LITHUANIAN = "lt" 83 | BELARUSIAN = "be" 84 | UKRAINIAN = "uk" 85 | KURDISH = "ku" 86 | TATAR = "tt" 87 | CHECHEN = "ce" 88 | UDMURT = "udm" 89 | KOMI = "kv" 90 | MARI = "chm" 91 | MORDVIN = "mdf" 92 | SAAMI = "se" 93 | FAROESE = "fo" 94 | GREENLANDIC = "kl" 95 | HAWAIIAN = "haw" 96 | MAORI = "mi" 97 | SAMOAN = "sm" 98 | TAHITIAN = "ty" 99 | TONGAN = "to" 100 | FIJIAN = "fj" 101 | MARSHALLESE = "mh" 102 | CHAMORRO = "ch" 103 | PALAUAN = "pau" 104 | KIRIBATI = "gil" 105 | NAURUAN = "na" 106 | TUVALUAN = "tvl" 107 | TOKELAUAN = "tkl" 108 | NIUEAN = "niu" 109 | COOK_ISLANDS_MAORI = "rar" 110 | HAITIAN_CREOLE = "ht" 111 | PAPIAMENTO = "pap" 112 | GUARANI = "gn" 113 | AYMARA = "ay" 114 | QUECHUA = "qu" 115 | MAPUDUNGUN = "arn" 116 | TUPI = "tpw" 117 | XHOSA = "xh" 118 | ZULU = "zu" 119 | AFRIKAANS = "af" 120 | SESOTHO = "st" 121 | TSWANA = "tn" 122 | VENDA = "ve" 123 | TSONGA = "ts" 124 | SWAZI = "ss" 125 | NDEBELE = "nr" 126 | SHONA = "sn" 127 | LUGANDA = "lg" 128 | KINYARWANDA = "rw" 129 | RUNDI = "rn" 130 | SOMALI = "so" 131 | OROMO = "om" 132 | AMHARIC = "am" 133 | TIGRINYA = "ti" 134 | WOLOF = "wo" 135 | FULA = "ff" 136 | MANDINKA = "mnk" 137 | MANDE = "man" 138 | KPELLE = "kpe" 139 | VAI = "vai" 140 | BASSA = "bsq" 141 | GREBO = "grb" 142 | KRU = "kro" 143 | GOLA = "gol" 144 | KISSI = "kis" 145 | LIMBA = "lia" 146 | KONO = "kno" 147 | KURANKO = "kur" 148 | LOKO = "lok" 149 | MANINKA = "mnk" 150 | MANDINGO = "man" 151 | SONINKE = "snk" 152 | SUSU = "sus" 153 | YALUNKA = "yal" 154 | BAMBARA = "bam" 155 | DYULA = "dyu" 156 | MALINKE = "mlq" 157 | MANJAK = "mjk" 158 | PAPEL = "pck" 159 | BALANTA = "blt" 160 | BIJAGO = "bjg" 161 | BUJUBU = "bjb" 162 | CRIOULO = "cri" 163 | FULFULDE = "fuv" 164 | HAUSA = "hau" 165 | IGBO = "ibo" 166 | YORUBA = "yor" 167 | EDO = "bin" 168 | EFIK = "efi" 169 | IGBIRA = "igb" 170 | IDOMA = "idu" 171 | ITSEKIRI = "its" 172 | IZON = "ijc" 173 | OKRIKA = "okr" 174 | URHOBO = "urh" 175 | ISOKO = "iso" 176 | KALABARI = "ijn" 177 | -------------------------------------------------------------------------------- /examples/flet_onesignal_example/src/main.py: -------------------------------------------------------------------------------- 1 | import flet as ft 2 | import flet_onesignal as fos 3 | from functools import partial 4 | 5 | ONESIGNAL_APP_ID = "example-123a-12a3-1a23-abcd1ef23g45" 6 | 7 | 8 | async def main(page: ft.Page): 9 | page.appbar = ft.AppBar(title=ft.Text("OneSignal Test"), bgcolor=ft.Colors.BLUE_700, color=ft.Colors.WHITE) 10 | get_onesignal_id = ft.TextField(label='Get OneSignal ID', read_only=True) 11 | get_external_id = ft.TextField(label='Get External User ID', read_only=True, ignore_pointers=True) 12 | set_external_id = ft.TextField(label='Set External User ID', hint_text='User ID') 13 | language = ft.TextField(label='Language', hint_text='Language Code (en)', value='en', color=ft.Colors.GREEN) 14 | 15 | def handle_notification_opened(e): 16 | #Access the data of the clicked notification 17 | list_view.content.controls.append(ft.Text(f"Notification opened: {e.notification_opened}")) 18 | list_view.update() 19 | 20 | def handle_notification_received(e): 21 | # Access the data of the received notification 22 | list_view.content.controls.append(ft.Text(f"Notification received: {e.notification_received}")) 23 | list_view.update() 24 | 25 | def handle_click_in_app_messages(e): 26 | # Access the data of the received notification in app messages 27 | list_view.content.controls.append(ft.Text(f"Notification click_in_app_messages: {e.click_in_app_messages}")) 28 | list_view.update() 29 | 30 | def get_id(e): 31 | result = onesignal.get_onesignal_id() 32 | get_onesignal_id.value = result 33 | get_onesignal_id.update() 34 | 35 | def get_external_user_id(e): 36 | result = onesignal.get_external_user_id() 37 | get_external_id.value = result 38 | get_external_id.update() 39 | 40 | def handle_login(e, external_user_id): 41 | message = "Login failed" 42 | 43 | if not external_user_id.value: 44 | message = "Please enter external user ID" 45 | 46 | if external_user_id.value: 47 | result = onesignal.login(external_user_id.value) 48 | if result: 49 | message = "Login successful" 50 | 51 | list_view.content.controls.append(ft.Text(message)) 52 | list_view.update() 53 | 54 | def handle_logout(e): 55 | onesignal.logout() 56 | set_external_id.value = None 57 | set_external_id.update() 58 | 59 | def set_language(e, language_code): 60 | result = onesignal.set_language(language_code.value) 61 | list_view.content.controls.append(ft.Text(result)) 62 | list_view.update() 63 | print(result) 64 | 65 | def handle_error(e): 66 | #handle_error 67 | list_view.content.controls.append(ft.Text(f"Error: {e.data}")) 68 | list_view.update() 69 | 70 | onesignal = fos.OneSignal( 71 | settings=fos.OneSignalSettings(app_id=ONESIGNAL_APP_ID), 72 | on_notification_opened=handle_notification_opened, 73 | on_notification_received=handle_notification_received, 74 | on_click_in_app_messages=handle_click_in_app_messages, 75 | on_error=handle_error, 76 | ) 77 | 78 | container = ft.Container( 79 | alignment=ft.alignment.bottom_center, 80 | content=ft.Row( 81 | scroll=ft.ScrollMode.ADAPTIVE, 82 | expand=True, 83 | vertical_alignment=ft.CrossAxisAlignment.CENTER, 84 | controls=[ 85 | ft.ElevatedButton( 86 | text='Get OneSignal Id', 87 | on_click=get_id 88 | ), 89 | ft.ElevatedButton( 90 | 'Get External User Id', 91 | on_click=get_external_user_id 92 | ), 93 | ft.ElevatedButton( 94 | text='Set External User Id', 95 | on_click=partial(handle_login, external_user_id=set_external_id) 96 | ), 97 | ft.ElevatedButton( 98 | text='Logout External User Id', 99 | on_click=handle_logout 100 | ), 101 | ft.ElevatedButton( 102 | text='Set Language', 103 | on_click=partial(set_language, language_code=language) 104 | ), 105 | ] 106 | ) 107 | ) 108 | 109 | list_view = ft.Container( 110 | expand=True, 111 | content=ft.ListView( 112 | padding=ft.padding.all(10), 113 | spacing=5, 114 | ) 115 | ) 116 | page.overlay.append(onesignal) 117 | 118 | page.add( 119 | # onesignal, 120 | list_view, 121 | get_onesignal_id, 122 | get_external_id, 123 | set_external_id, 124 | language, 125 | container, 126 | ) 127 | 128 | 129 | if __name__ == "__main__": 130 | ft.app(target=main) 131 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.tmp 7 | *.lock 8 | .DS_Store 9 | .atom/ 10 | .buildlog/ 11 | .history 12 | .svn/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ-related files 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # Visual Studio Code-related files 22 | # Uncomment the following line if you wish to ignore VS Code configurations. 23 | #.vscode/ 24 | .vscode/ 25 | 26 | # Flutter/Dart/Pub-related files 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub/ 32 | .pub-cache/ 33 | pubspec.lock 34 | flutter_export_environment.sh 35 | **/doc/api/ 36 | 37 | # Build outputs 38 | build/ 39 | 40 | # Android-specific files 41 | android/.idea 42 | android/.cxx/ 43 | android/local.properties 44 | 45 | # iOS-specific files 46 | ios/Flutter/.last_build_id 47 | ios/Flutter/App.framework 48 | ios/Flutter/Flutter.framework 49 | ios/Flutter/Flutter.podspec 50 | ios/ServiceDefinitions.json 51 | ios/Runner/GeneratedPluginRegistrant.* 52 | 53 | # macOS-specific files 54 | macos/Flutter/ephemeral/ 55 | 56 | # Web-related files 57 | web/.dart_tool/ 58 | 59 | # Windows-specific files 60 | windows/flutter/ephemeral/ 61 | 62 | # Linux-specific files 63 | linux/flutter/ephemeral/ 64 | 65 | # Generated metadata 66 | .metadata 67 | 68 | # Python ### 69 | # Byte-compiled / optimized / DLL files 70 | __pycache__/ 71 | *.py[cod] 72 | *$py.class 73 | 74 | # C extensions 75 | *.so 76 | 77 | # Distribution / packaging 78 | .Python 79 | #build/ 80 | develop-eggs/ 81 | dist/ 82 | downloads/ 83 | eggs/ 84 | .eggs/ 85 | #lib/ 86 | lib64/ 87 | parts/ 88 | sdist/ 89 | var/ 90 | wheels/ 91 | share/python-wheels/ 92 | *.egg-info/ 93 | .installed.cfg 94 | *.egg 95 | MANIFEST 96 | 97 | # PyInstaller 98 | # Usually these files are written by a python script from a template 99 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 100 | *.manifest 101 | *.spec 102 | 103 | # Installer logs 104 | pip-log.txt 105 | pip-delete-this-directory.txt 106 | 107 | # Unit test / coverage reports 108 | htmlcov/ 109 | .tox/ 110 | .nox/ 111 | .coverage 112 | .coverage.* 113 | .cache 114 | nosetests.xml 115 | coverage.xml 116 | *.cover 117 | *.py,cover 118 | .hypothesis/ 119 | .pytest_cache/ 120 | cover/ 121 | 122 | # Translations 123 | *.mo 124 | *.pot 125 | 126 | # Django stuff: 127 | #*.log 128 | local_settings.py 129 | db.sqlite3 130 | db.sqlite3-journal 131 | 132 | # Flask stuff: 133 | instance/ 134 | .webassets-cache 135 | 136 | # Scrapy stuff: 137 | .scrapy 138 | 139 | # Sphinx documentation 140 | docs/_build/ 141 | 142 | # PyBuilder 143 | .pybuilder/ 144 | target/ 145 | 146 | # Jupyter Notebook 147 | .ipynb_checkpoints 148 | 149 | # IPython 150 | profile_default/ 151 | ipython_config.py 152 | 153 | # pyenv 154 | # For a library or package, you might want to ignore these files since the code is 155 | # intended to run in multiple environments; otherwise, check them in: 156 | # .python-version 157 | 158 | # pipenv 159 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 160 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 161 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 162 | # install all needed dependencies. 163 | #Pipfile.lock 164 | 165 | # poetry 166 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 167 | # This is especially recommended for binary packages to ensure reproducibility, and is more 168 | # commonly ignored for libraries. 169 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 170 | #poetry.lock 171 | 172 | # pdm 173 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 174 | #pdm.lock 175 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 176 | # in version control. 177 | # https://pdm.fming.dev/#use-with-ide 178 | .pdm.toml 179 | 180 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 181 | __pypackages__/ 182 | 183 | # Celery stuff 184 | celerybeat-schedule 185 | celerybeat.pid 186 | 187 | # SageMath parsed files 188 | *.sage.py 189 | 190 | # Environments 191 | .env 192 | .venv 193 | env/ 194 | venv/ 195 | ENV/ 196 | env.bak/ 197 | venv.bak/ 198 | 199 | # Spyder project settings 200 | .spyderproject 201 | .spyproject 202 | 203 | # Rope project settings 204 | .ropeproject 205 | 206 | # mkdocs documentation 207 | /site 208 | 209 | # mypy 210 | .mypy_cache/ 211 | .dmypy.json 212 | dmypy.json 213 | 214 | # Pyre type checker 215 | .pyre/ 216 | 217 | # pytype static type analyzer 218 | .pytype/ 219 | 220 | # Cython debug symbols 221 | cython_debug/ 222 | 223 | # PyCharm 224 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 225 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 226 | # and can be added to the global gitignore or merged into this file. For a more nuclear 227 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 228 | #.idea/ 229 | 230 | ### Python Patch ### 231 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 232 | poetry.toml 233 | 234 | # ruff 235 | .ruff_cache/ 236 | 237 | # LSP config files 238 | pyrightconfig.json 239 | 240 | #main.py 241 | src/flutter/flet_onesignal/analysis_options.yaml 242 | src/flutter/flet_onesignal/.fvmrc 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Flet OneSignal

2 | 3 | 4 |

Flet OneSignal

5 | 6 | ## 📖 Overview 7 | 8 | Flet OneSignal is an extension for Flet in Python, integrating the OneSignal package from Dart/Flutter. It enables push notifications and messaging for mobile apps, making it easier to connect your iOS and Android applications with OneSignal. 9 | 10 | ## ☕ Buy Me a Coffee 11 | If you liked this project, please consider supporting its development with a donation. Your contribution will help me maintain and improve it. 12 | 13 | 14 | Buy Me a Coffee 15 | 16 | 17 | ## 📦 Installation 18 | ##### You can install `flet-onesignal` using one of the following package managers: 19 | 20 | **POETRY** 21 | 22 | ```console 23 | $ poetry add flet-onesignal 24 | ``` 25 | 26 | **PIP** 27 | 28 | ```console 29 | $ pip install flet-onesignal 30 | ``` 31 | 32 | **UV** 33 | 34 | ```console 35 | $ uv add flet-onesignal 36 | ``` 37 | 38 | --- 39 | 40 | ## 🛠️ Configuration in the pyproject.toml file. 41 | 42 | [More in ](https://flet.dev/blog/pyproject-toml-support-for-flet-build-command/) Support for flet build command. 43 | 44 | ```toml 45 | [project] 46 | name = "flet-onesignal-example" 47 | version = "0.1.0" 48 | description = "flet-onesignal-example" 49 | readme = "README.md" 50 | requires-python = ">=3.12" 51 | authors = [ 52 | { name = "developer", email = "you@example.com" } 53 | ] 54 | 55 | dependencies = [ 56 | "flet>=0.26.0", 57 | "flet-onesignal>=0.3.0", 58 | ] 59 | 60 | [tool.uv] 61 | dev-dependencies = [ 62 | "flet[all]>=0.26.0", 63 | ] 64 | 65 | ``` 66 | 67 | ## 🔥 Example 68 | 69 | ```Python 70 | import flet as ft 71 | import flet_onesignal as fos 72 | from functools import partial 73 | 74 | ONESIGNAL_APP_ID = "example-123a-12a3-1a23-abcd1ef23g45" 75 | 76 | 77 | async def main(page: ft.Page): 78 | page.appbar = ft.AppBar(title=ft.Text("OneSignal Test"), bgcolor=ft.Colors.BLUE_700, color=ft.Colors.WHITE) 79 | get_onesignal_id = ft.TextField(label='Get OneSignal ID', read_only=True) 80 | get_external_id = ft.TextField(label='Get External User ID', read_only=True, ignore_pointers=True) 81 | set_external_id = ft.TextField(label='Set External User ID', hint_text='User ID') 82 | language = ft.TextField(label='Language', hint_text='Language Code (en)', value='en', color=ft.Colors.GREEN) 83 | 84 | def handle_notification_opened(e): 85 | #Access the data of the clicked notification 86 | list_view.content.controls.append(ft.Text(f"Notification opened: {e.notification_opened}")) 87 | list_view.update() 88 | 89 | def handle_notification_received(e): 90 | # Access the data of the received notification 91 | list_view.content.controls.append(ft.Text(f"Notification received: {e.notification_received}")) 92 | list_view.update() 93 | 94 | def handle_click_in_app_messages(e): 95 | # Access the data of the received notification in app messages 96 | list_view.content.controls.append(ft.Text(f"Notification click_in_app_messages: {e.click_in_app_messages}")) 97 | list_view.update() 98 | 99 | def get_id(e): 100 | result = onesignal.get_onesignal_id() 101 | get_onesignal_id.value = result 102 | get_onesignal_id.update() 103 | 104 | def get_external_user_id(e): 105 | result = onesignal.get_external_user_id() 106 | get_external_id.value = result 107 | get_external_id.update() 108 | 109 | def handle_login(e, external_user_id): 110 | message = "Login failed" 111 | 112 | if not external_user_id.value: 113 | message = "Please enter external user ID" 114 | 115 | if external_user_id.value: 116 | result = onesignal.login(external_user_id.value) 117 | if result: 118 | message = "Login successful" 119 | 120 | list_view.content.controls.append(ft.Text(message)) 121 | list_view.update() 122 | 123 | def handle_logout(e): 124 | onesignal.logout() 125 | set_external_id.value = None 126 | set_external_id.update() 127 | 128 | def set_language(e, language_code): 129 | result = onesignal.set_language(language_code.value) 130 | list_view.content.controls.append(ft.Text(result)) 131 | list_view.update() 132 | print(result) 133 | 134 | def handle_error(e): 135 | #handle_error 136 | list_view.content.controls.append(ft.Text(f"Error: {e.data}")) 137 | list_view.update() 138 | 139 | onesignal = fos.OneSignal( 140 | settings=fos.OneSignalSettings(app_id=ONESIGNAL_APP_ID), 141 | on_notification_opened=handle_notification_opened, 142 | on_notification_received=handle_notification_received, 143 | on_click_in_app_messages=handle_click_in_app_messages, 144 | on_error=handle_error, 145 | ) 146 | 147 | container = ft.Container( 148 | alignment=ft.alignment.bottom_center, 149 | content=ft.Row( 150 | scroll=ft.ScrollMode.ADAPTIVE, 151 | expand=True, 152 | vertical_alignment=ft.CrossAxisAlignment.CENTER, 153 | controls=[ 154 | ft.ElevatedButton( 155 | text='Get OneSignal Id', 156 | on_click=get_id 157 | ), 158 | ft.ElevatedButton( 159 | 'Get External User Id', 160 | on_click=get_external_user_id 161 | ), 162 | ft.ElevatedButton( 163 | text='Set External User Id', 164 | on_click=partial(handle_login, external_user_id=set_external_id) 165 | ), 166 | ft.ElevatedButton( 167 | text='Logout External User Id', 168 | on_click=handle_logout 169 | ), 170 | ft.ElevatedButton( 171 | text='Set Language', 172 | on_click=partial(set_language, language_code=language) 173 | ), 174 | ] 175 | ) 176 | ) 177 | 178 | list_view = ft.Container( 179 | expand=True, 180 | content=ft.ListView( 181 | padding=ft.padding.all(10), 182 | spacing=5, 183 | ) 184 | ) 185 | page.overlay.append(onesignal) 186 | 187 | page.add( 188 | # onesignal, 189 | list_view, 190 | get_onesignal_id, 191 | get_external_id, 192 | set_external_id, 193 | language, 194 | container, 195 | ) 196 | 197 | 198 | if __name__ == "__main__": 199 | ft.app(target=main) 200 | 201 | ``` 202 | ## 🤝🏽 Contributing 203 | Contributions and feedback are welcome! 204 | 205 | #### To contribute: 206 | 207 | 1. **Fork the repository.** 208 | 2. **Create a feature branch.** 209 | 3. **Submit a pull request with a detailed explanation of your changes.** 210 | 211 | --- 212 | 213 | ## 🚀 Try **flet-onesignal** today and enhance your Flet apps with push notifications!🔔 214 | 215 | 216 | 217 | [Commit your work to the LORD, and your plans will succeed. Proverbs 16: 3](https://www.bible.com/bible/116/PRO.16.NLT) 218 | -------------------------------------------------------------------------------- /src/flutter/flet_onesignal/lib/src/flet_onesignal.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flet/flet.dart'; 3 | import 'package:flet_onesignal/src/utils/funcs.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:onesignal_flutter/onesignal_flutter.dart'; 6 | 7 | class FletOneSignalControl extends StatefulWidget { 8 | final Control? parent; 9 | final Control control; 10 | final FletControlBackend backend; 11 | 12 | const FletOneSignalControl({ 13 | super.key, 14 | required this.parent, 15 | required this.control, 16 | required this.backend, 17 | }); 18 | 19 | @override 20 | State createState() => _FletOneSignalControlState(); 21 | } 22 | 23 | class _FletOneSignalControlState extends State 24 | with FletStoreMixin { 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _initializeOneSignal(); 29 | } 30 | 31 | @override 32 | void didChangeDependencies() { 33 | super.didChangeDependencies(); 34 | _setupNotificationHandler(); 35 | } 36 | 37 | void _initializeOneSignal() { 38 | final settings = widget.control.attrString("settings", "{}")!; 39 | 40 | Map settingsData; 41 | 42 | if (settings != "{}") { 43 | try { 44 | settingsData = jsonDecode(settings); 45 | final appId = settingsData["app_id"] ?? "DEFAULT_APP_ID"; 46 | OneSignal.initialize(appId); 47 | } catch (error, stackTrace) { 48 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 49 | handleFlutterError( 50 | method: "_initializeOneSignal", 51 | error: error, 52 | stackTrace: stackTrace, 53 | trigger: widget.backend.triggerControlEvent, 54 | controlId: widget.control.id, 55 | ); 56 | } 57 | } 58 | } 59 | 60 | void _setupNotificationHandler() { 61 | OneSignal.Notifications.addClickListener((event) { 62 | try { 63 | debugPrint("Notifications-addClickListener"); 64 | 65 | final jsonData = jsonEncode({ 66 | "json_data": event.notification.jsonRepresentation(), 67 | }); 68 | 69 | widget.backend.triggerControlEvent( 70 | widget.control.id, 71 | "notification_received", 72 | jsonData, 73 | ); 74 | } catch (error, stackTrace) { 75 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 76 | handleFlutterError( 77 | method: "_setupNotificationHandler", 78 | error: error, 79 | stackTrace: stackTrace, 80 | trigger: widget.backend.triggerControlEvent, 81 | controlId: widget.control.id, 82 | ); 83 | } 84 | }); 85 | 86 | OneSignal.Notifications.addForegroundWillDisplayListener((event) { 87 | try { 88 | debugPrint("Notifications-addForegroundWillDisplayListener"); 89 | 90 | final jsonData = jsonEncode({ 91 | "json_data": event.notification.jsonRepresentation(), 92 | }); 93 | 94 | widget.backend.triggerControlEvent(widget.control.id, "notification_opened", jsonData); 95 | 96 | } catch (error, stackTrace) { 97 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 98 | handleFlutterError( 99 | method: "_setupNotificationHandler", 100 | error: error, 101 | stackTrace: stackTrace, 102 | trigger: widget.backend.triggerControlEvent, 103 | controlId: widget.control.id, 104 | ); 105 | } 106 | }); 107 | 108 | OneSignal.InAppMessages.addClickListener((event) async { 109 | try { 110 | debugPrint("InAppMessages-addClickListener"); 111 | 112 | var messageMap = jsonDecode(event.message.jsonRepresentation()); 113 | var resultMap = jsonDecode(event.result.jsonRepresentation()); 114 | 115 | final jsonData = jsonEncode({ 116 | "json_data": { 117 | "message": messageMap, 118 | "result": resultMap, 119 | } 120 | }); 121 | 122 | widget.backend.triggerControlEvent(widget.control.id, "click_in_app_messages", jsonData); 123 | 124 | } catch (error, stackTrace) { 125 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 126 | handleFlutterError( 127 | method: "_setupNotificationHandler", 128 | error: error, 129 | stackTrace: stackTrace, 130 | trigger: widget.backend.triggerControlEvent, 131 | controlId: widget.control.id, 132 | ); 133 | } 134 | }); 135 | 136 | OneSignal.InAppMessages.addWillDisplayListener((event) async { 137 | try { 138 | debugPrint("InAppMessages-addWillDisplayListener"); 139 | 140 | var messageMap = jsonDecode(event.message.jsonRepresentation()); 141 | 142 | final jsonData = jsonEncode({ 143 | "json_data": messageMap, 144 | }); 145 | 146 | debugPrint("dataStr: $jsonData"); 147 | 148 | widget.backend.triggerControlEvent( 149 | widget.control.id, "will_display_in_app_messages", jsonData); 150 | } catch (error, stackTrace) { 151 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 152 | handleFlutterError( 153 | method: "_setupNotificationHandler", 154 | error: error, 155 | stackTrace: stackTrace, 156 | trigger: widget.backend.triggerControlEvent, 157 | controlId: widget.control.id, 158 | ); 159 | } 160 | }); 161 | 162 | OneSignal.InAppMessages.addDidDisplayListener((event) async { 163 | try { 164 | debugPrint("InAppMessages-addDidDisplayListener"); 165 | 166 | var messageMap = jsonDecode(event.message.jsonRepresentation()); 167 | 168 | final jsonData = jsonEncode({ 169 | "json_data": messageMap, 170 | }); 171 | 172 | debugPrint("dataStr: $jsonData"); 173 | 174 | widget.backend.triggerControlEvent( 175 | widget.control.id, "did_display_in_app_messages", jsonData); 176 | } catch (error, stackTrace) { 177 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 178 | handleFlutterError( 179 | method: "_setupNotificationHandler", 180 | error: error, 181 | stackTrace: stackTrace, 182 | trigger: widget.backend.triggerControlEvent, 183 | controlId: widget.control.id, 184 | ); 185 | } 186 | }); 187 | 188 | OneSignal.InAppMessages.addWillDismissListener((event) async { 189 | try { 190 | debugPrint("InAppMessages-addWillDismissListener"); 191 | 192 | var messageMap = jsonDecode(event.message.jsonRepresentation()); 193 | 194 | final jsonData = jsonEncode({ 195 | "json_data": messageMap, 196 | }); 197 | 198 | debugPrint("dataStr: $jsonData"); 199 | 200 | widget.backend.triggerControlEvent( 201 | widget.control.id, "will_dismiss_in_app_messages", jsonData); 202 | } catch (error, stackTrace) { 203 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 204 | handleFlutterError( 205 | method: "_setupNotificationHandler", 206 | error: error, 207 | stackTrace: stackTrace, 208 | trigger: widget.backend.triggerControlEvent, 209 | controlId: widget.control.id, 210 | ); 211 | } 212 | }); 213 | 214 | OneSignal.InAppMessages.addDidDismissListener((event) async { 215 | try { 216 | debugPrint("InAppMessages-addDidDismissListener"); 217 | 218 | var messageMap = jsonDecode(event.message.jsonRepresentation()); 219 | 220 | final jsonData = jsonEncode({ 221 | "json_data": messageMap, 222 | }); 223 | 224 | debugPrint("dataStr: $jsonData"); 225 | 226 | widget.backend.triggerControlEvent( 227 | widget.control.id, "did_dismiss_in_app_messages", jsonData); 228 | } catch (error, stackTrace) { 229 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 230 | handleFlutterError( 231 | method: "_setupNotificationHandler", 232 | error: error, 233 | stackTrace: stackTrace, 234 | trigger: widget.backend.triggerControlEvent, 235 | controlId: widget.control.id, 236 | ); 237 | } 238 | }); 239 | } 240 | 241 | @override 242 | Widget build(BuildContext context) { 243 | debugPrint("OneSignal build: ${widget.control.id} (${widget.control.hashCode})"); 244 | 245 | () async { 246 | try { 247 | widget.backend.subscribeMethods(widget.control.id, 248 | (methodName, args) async { 249 | try { 250 | return switch (methodName) { 251 | "get_onesignal_id" => _getOnesignalId(), 252 | "get_external_user_id" => _getExternalUserId(), 253 | "login" => _login(args), 254 | "logout" => _logout(), 255 | "add_alias" => _addAlias(args), 256 | "remove_alias" => _removeAlias(args), 257 | "set_language" => _setLanguage(args), 258 | "consent_required" => _consentRequired(args), 259 | "request_permission" => _requestPermission(args), 260 | "opt_in" => _optIn(), 261 | "opt_out" => _optOut(), 262 | "register_for_provisional_authorization" => _registerForProvisionalAuthorization(), 263 | "can_request_permission" => _canRequestPermission(), 264 | "remove_notification" => _removeNotification(args), 265 | "remove_grouped_notifications" => _removeGroupedNotifications(args), 266 | "clear_all_notifications" => _clearAllNotifications(), 267 | "prevent_default" => _preventDefault(args), 268 | _ => null, 269 | }; 270 | } catch (error, stackTrace) { 271 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 272 | handleFlutterError( 273 | method: methodName, 274 | error: error, 275 | stackTrace: stackTrace, 276 | trigger: widget.backend.triggerControlEvent, 277 | controlId: widget.control.id, 278 | ); 279 | return error.toString(); 280 | } 281 | }); 282 | } catch (error, stackTrace) { 283 | debugPrint("Error:\nerror: $error\nstackTrace: $stackTrace"); 284 | handleFlutterError( 285 | method: "subscribeMethods", 286 | error: error, 287 | stackTrace: stackTrace, 288 | trigger: widget.backend.triggerControlEvent, 289 | controlId: widget.control.id, 290 | ); 291 | } 292 | }(); 293 | 294 | return const SizedBox.shrink(); 295 | } 296 | 297 | Future _getOnesignalId() async { 298 | var result = await OneSignal.User.getOnesignalId() ?? "The OneSignal ID does not exist."; 299 | return result; 300 | } 301 | 302 | Future _getExternalUserId() async { 303 | var result = await OneSignal.User.getExternalId() ?? "The external user ID does not yet exist."; 304 | return result; 305 | } 306 | 307 | Future _login(Map args) async { 308 | var externalUserId = args["external_user_id"] ?? ""; 309 | await OneSignal.login(externalUserId); 310 | return null; 311 | } 312 | 313 | Future _logout() async { 314 | await OneSignal.logout(); 315 | return null; 316 | } 317 | 318 | Future _addAlias(Map args) async { 319 | String alias = args["alias"] ?? ""; 320 | dynamic idAlias = args["id_alias"]; 321 | await OneSignal.User.addAlias(alias, idAlias); 322 | return null; 323 | } 324 | 325 | Future _removeAlias(Map args) async { 326 | String alias = args["alias"] ?? ""; 327 | await OneSignal.User.removeAlias(alias); 328 | return null; 329 | } 330 | 331 | Future _setLanguage(Map args) async { 332 | String language = args["language"] ?? "en"; 333 | await OneSignal.User.setLanguage(language); 334 | return null; 335 | } 336 | 337 | Future _consentRequired(Map args) async { 338 | String dataStr = args["data"]!; 339 | Map dataMap = json.decode(dataStr); 340 | bool consent = dataMap["consent"] as bool; 341 | await OneSignal.consentRequired(consent); 342 | return null; 343 | } 344 | 345 | Future _requestPermission(Map args) async { 346 | String dataStr = args["data"]!; 347 | Map dataMap = json.decode(dataStr); 348 | bool fallbackToSettings = dataMap["fallback_to_settings"] as bool; 349 | var result = await OneSignal.Notifications.requestPermission(fallbackToSettings); 350 | return result.toString(); 351 | } 352 | 353 | Future _optIn() async { 354 | await OneSignal.User.pushSubscription.optIn(); 355 | return null; 356 | } 357 | 358 | Future _optOut() async { 359 | await OneSignal.User.pushSubscription.optOut(); 360 | return null; 361 | } 362 | 363 | Future _registerForProvisionalAuthorization() async { 364 | var result = await OneSignal.Notifications.registerForProvisionalAuthorization(true); 365 | return result.toString(); 366 | } 367 | 368 | Future _canRequestPermission() async { 369 | var result = await OneSignal.Notifications.canRequest(); 370 | return result.toString(); 371 | } 372 | 373 | Future _removeNotification(Map args) async { 374 | int notificationId = args["notification_id"] as int; 375 | await OneSignal.Notifications.removeNotification(notificationId); 376 | return null; 377 | } 378 | 379 | Future _removeGroupedNotifications(Map args) async { 380 | var notificationGroup = args["notification_group"] ?? ""; 381 | await OneSignal.Notifications.removeGroupedNotifications( 382 | notificationGroup); 383 | return null; 384 | } 385 | 386 | Future _clearAllNotifications() async { 387 | await OneSignal.Notifications.clearAll(); 388 | return null; 389 | } 390 | 391 | Future _preventDefault(Map args) async { 392 | var notificationId = args["notification_id"] ?? ""; 393 | OneSignal.Notifications.preventDefault(notificationId); 394 | return null; 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/flet_onesignal/flet_onesignal.py: -------------------------------------------------------------------------------- 1 | import json 2 | from flet.core.ref import Ref 3 | from typing import Any, Optional 4 | from dataclasses import dataclass 5 | from flet.core.control import Control 6 | from flet_onesignal.languages import Language 7 | from flet.core.event_handler import EventHandler 8 | from flet.core.control_event import ControlEvent 9 | from flet.core.types import OptionalEventCallable, OptionalControlEventCallable 10 | 11 | 12 | @dataclass 13 | class OneSignalSettings: 14 | app_id: str 15 | 16 | 17 | @dataclass 18 | class OneSignalChangeEvent(ControlEvent): 19 | def __init__(self, e: ControlEvent): 20 | super().__init__(e.target, e.name, e.data, e.control, e.page) 21 | data = json.loads(e.data) 22 | self.notification_received: dict = data.get("json_data") 23 | self.notification_opened: dict = data.get("json_data") 24 | self.click_in_app_messages : dict = data.get("json_data") 25 | self.will_display_in_app_messages: dict = data.get("json_data") 26 | self.did_display_in_app_messages: dict = data.get("json_data") 27 | self.will_dismiss_in_app_messages: dict = data.get("json_data") 28 | self.did_dismiss_in_app_messages: dict = data.get("json_data") 29 | 30 | 31 | class OneSignal(Control): 32 | """ 33 | A control that allows you to send push notifications and messages to mobile applications. This control makes it easy 34 | to integrate your iOS and/or Android applications with OneSignal. 35 | 36 | This control is not visual and should be added to the `page.overlay` list. 37 | """ 38 | 39 | def __init__( 40 | self, 41 | # Control 42 | # 43 | ref: Optional[Ref] = None, 44 | data: Any = None, 45 | settings: OneSignalSettings = None, 46 | on_notification_opened: OptionalEventCallable[OneSignalChangeEvent] = None, 47 | on_notification_received: OptionalEventCallable[OneSignalChangeEvent] = None, 48 | on_click_in_app_messages: OptionalEventCallable[OneSignalChangeEvent] = None, 49 | on_will_display_in_app_messages: OptionalEventCallable[OneSignalChangeEvent] = None, 50 | on_did_display_in_app_messages: OptionalEventCallable[OneSignalChangeEvent] = None, 51 | on_will_dismiss_in_app_messages: OptionalEventCallable[OneSignalChangeEvent] = None, 52 | on_did_dismiss_in_app_messages: OptionalEventCallable[OneSignalChangeEvent] = None, 53 | on_error: OptionalControlEventCallable = None, 54 | ): 55 | Control.__init__( 56 | self, 57 | ref=ref, 58 | data=data, 59 | ) 60 | # handlers attributes 61 | self.__on_notification_opened = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 62 | self._add_event_handler(event_name="notification_opened", handler=self.__on_notification_opened.get_handler()) 63 | self.__on_notification_received = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 64 | self._add_event_handler(event_name="notification_received", handler=self.__on_notification_received.get_handler()) 65 | self.__on_click_in_app_messages = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 66 | self._add_event_handler(event_name="click_in_app_messages", handler=self.__on_click_in_app_messages.get_handler()) 67 | self.__on_will_display_in_app_messages = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 68 | self._add_event_handler(event_name="will_display_in_app_messages", handler=self.__on_will_display_in_app_messages.get_handler()) 69 | self.__on_did_display_in_app_messages = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 70 | self._add_event_handler(event_name="did_display_in_app_messages", handler=self.__on_did_display_in_app_messages.get_handler()) 71 | self.__on_will_dismiss_in_app_messages = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 72 | self._add_event_handler(event_name="will_dismiss_in_app_messages", handler=self.__on_will_dismiss_in_app_messages.get_handler()) 73 | self.__on_did_dismiss_in_app_messages = EventHandler(result_converter=lambda e: OneSignalChangeEvent(e)) 74 | self._add_event_handler(event_name="did_dismiss_in_app_messages", handler=self.__on_did_dismiss_in_app_messages.get_handler()) 75 | # 76 | # 77 | self.on_notification_opened = on_notification_opened 78 | self.on_notification_received = on_notification_received 79 | self.on_click_in_app_messages = on_click_in_app_messages 80 | self.on_will_display_in_app_messages = on_will_display_in_app_messages 81 | self.on_did_display_in_app_messages = on_did_display_in_app_messages 82 | self.on_will_dismiss_in_app_messages = on_will_dismiss_in_app_messages 83 | self.on_did_dismiss_in_app_messages = on_did_dismiss_in_app_messages 84 | self.on_error = on_error 85 | self.settings = settings 86 | 87 | def _get_control_name(self): 88 | return "flet_onesignal" 89 | 90 | def before_update(self): 91 | if self.settings is not None: 92 | self._set_attr_json("settings", self.settings) 93 | 94 | def get_onesignal_id(self, wait_timeout: Optional[float] = 25) -> str: 95 | """Returns the OneSignal ID for the current user, which may be None.""" 96 | 97 | result = self.invoke_method( 98 | method_name="get_onesignal_id", 99 | wait_for_result=True, 100 | wait_timeout=wait_timeout 101 | ) 102 | 103 | return result 104 | 105 | async def get_onesignal_id_async(self, wait_timeout: Optional[float] = 25) -> str: 106 | """Returns the OneSignal ID for the current user, which may be None.""" 107 | 108 | result = await self.invoke_method_async( 109 | method_name="get_onesignal_id", 110 | wait_for_result=True, 111 | wait_timeout=wait_timeout 112 | ) 113 | 114 | return result 115 | 116 | def get_external_user_id(self, wait_timeout: Optional[float] = 25): 117 | """Returns the External ID for the current user, which may be None.""" 118 | 119 | result = self.invoke_method( 120 | method_name="get_external_user_id", 121 | wait_for_result=True, 122 | wait_timeout=wait_timeout 123 | ) 124 | 125 | return result 126 | 127 | async def get_external_user_id_async(self, wait_timeout: Optional[float] = 25): 128 | """Returns the External ID for the current user, which may be None.""" 129 | 130 | result = await self.invoke_method_async( 131 | method_name="get_external_user_id", 132 | wait_for_result=True, 133 | wait_timeout=wait_timeout 134 | ) 135 | 136 | return result 137 | 138 | def login(self, external_user_id: str) -> bool: 139 | """Login to OneSignal under the user identified by the [external_user_id] provided. The act of logging a user into 140 | the OneSignal SDK will switch the user context to that specific user.""" 141 | 142 | self.invoke_method( 143 | method_name="login", 144 | arguments={"external_user_id": external_user_id}, 145 | wait_for_result=True, 146 | ) 147 | 148 | if self.get_external_user_id(): 149 | return True 150 | 151 | return False 152 | 153 | async def login_async(self, external_user_id: str) -> bool: 154 | """Login to OneSignal under the user identified by the [external_user_id] provided. The act of logging a user into 155 | the OneSignal SDK will switch the user context to that specific user.""" 156 | 157 | await self.invoke_method_async( 158 | method_name="login", 159 | arguments={"external_user_id": external_user_id}, 160 | wait_for_result=True, 161 | ) 162 | 163 | if await self.get_external_user_id_async(): 164 | return True 165 | 166 | return False 167 | 168 | 169 | def logout(self) -> None: 170 | """Logout the user previously logged in via [login]. The user property now references a new device-scoped user. 171 | A device-scoped user has no user identity that can later be retrieved, except through this device as long as the 172 | app remains installed and the app data is not cleared.""" 173 | 174 | self.invoke_method( 175 | method_name="logout", 176 | wait_for_result=True, 177 | ) 178 | 179 | async def logout_async(self) -> None: 180 | """Logout the user previously logged in via [login]. The user property now references a new device-scoped user. 181 | A device-scoped user has no user identity that can later be retrieved, except through this device as long as the 182 | app remains installed and the app data is not cleared.""" 183 | 184 | await self.invoke_method_async( 185 | method_name="logout", 186 | wait_for_result=True, 187 | ) 188 | 189 | def add_alias(self, alias: str, id_alias: any) -> None: 190 | """Set an [alias] for the current user. If this [alias] label already exists on this user, it will be 191 | overwritten with the new alias [id].""" 192 | 193 | args = { 194 | 'alias': alias, 195 | 'id_alias': id_alias, 196 | } 197 | 198 | self.invoke_method( 199 | method_name="add_alias", 200 | arguments=args, 201 | wait_for_result=True, 202 | ) 203 | 204 | async def add_alias_async(self, alias: str, id_alias: any) -> None: 205 | """Set an [alias] for the current user. If this [alias] label already exists on this user, it will be 206 | overwritten with the new alias [id].""" 207 | 208 | args = { 209 | 'alias': alias, 210 | 'id_alias': id_alias, 211 | } 212 | 213 | await self.invoke_method_async( 214 | method_name="add_alias", 215 | arguments=args, 216 | wait_for_result=True, 217 | ) 218 | 219 | def remove_alias(self, alias: str) -> None: 220 | """Remove an [alias] from the current user.""" 221 | 222 | self.invoke_method( 223 | method_name="remove_alias", 224 | arguments={'alias': alias}, 225 | wait_for_result=True, 226 | ) 227 | 228 | async def remove_alias_async(self, alias: str) -> None: 229 | """Remove an [alias] from the current user.""" 230 | 231 | await self.invoke_method_async( 232 | method_name="remove_alias", 233 | arguments={'alias': alias}, 234 | wait_for_result=True, 235 | ) 236 | 237 | def set_language(self, language_code: str = 'en') -> str: 238 | """Sets the user's language. 239 | Sets the user's language to [language] this also applies to the email and/or SMS player if those are logged in 240 | on the device.""" 241 | 242 | if language_code in Language._value2member_map_: 243 | self.invoke_method( 244 | method_name="set_language", 245 | arguments={'language': language_code}, 246 | wait_for_result=True, 247 | ) 248 | return 'Language set successfully.' 249 | 250 | return 'Language not found.' 251 | 252 | async def set_language_async(self, language_code: str = 'en') -> str: 253 | """Sets the user's language. 254 | Sets the user's language to [language] this also applies to the email and/or SMS player if those are logged in 255 | on the device.""" 256 | 257 | if language_code in Language._value2member_map_: 258 | await self.invoke_method_async( 259 | method_name="set_language", 260 | arguments={'language': language_code}, 261 | wait_for_result=True, 262 | ) 263 | return 'Language set successfully.' 264 | 265 | return 'Language not found.' 266 | 267 | def remove_notification(self, notification_id: int) -> None: 268 | """Removes a single notification on Android devices.""" 269 | 270 | platform = self.page.platform.value 271 | 272 | if platform == 'android': 273 | self.invoke_method( 274 | method_name="remove_notification", 275 | arguments={"notification_id": str(notification_id)}, 276 | wait_for_result=True, 277 | ) 278 | 279 | async def remove_notification_async(self, notification_id: int) -> None: 280 | """Removes a single notification on Android devices.""" 281 | 282 | platform = self.page.platform.value 283 | 284 | if platform == 'android': 285 | await self.invoke_method_async( 286 | method_name="remove_notification", 287 | arguments={"notification_id": str(notification_id)}, 288 | wait_for_result=True, 289 | ) 290 | 291 | def remove_grouped_notifications(self, notification_group: str) -> None: 292 | """Removes a grouped notification on Android devices.""" 293 | 294 | platform = self.page.platform.value 295 | 296 | if platform == 'android': 297 | self.invoke_method( 298 | method_name="remove_grouped_notifications", 299 | arguments={"notification_group": notification_group}, 300 | wait_for_result=True, 301 | ) 302 | 303 | async def remove_grouped_notifications_async(self, notification_group: str) -> None: 304 | """Removes a grouped notification on Android devices.""" 305 | 306 | platform = self.page.platform.value 307 | 308 | if platform == 'android': 309 | await self.invoke_method_async( 310 | method_name="remove_grouped_notifications", 311 | arguments={"notification_group": notification_group}, 312 | wait_for_result=True, 313 | ) 314 | 315 | def clear_all_notifications(self) -> None: 316 | """Removes all OneSignal notifications.""" 317 | 318 | self.invoke_method( 319 | method_name="clear_all_notifications", 320 | wait_for_result=True, 321 | ) 322 | 323 | async def clear_all_notifications_async(self) -> None: 324 | """Removes all OneSignal notifications.""" 325 | 326 | await self.invoke_method_async( 327 | method_name="clear_all_notifications", 328 | wait_for_result=True, 329 | ) 330 | 331 | def consent_required(self, consent: bool = True) -> None: 332 | """Allows you to completely disable the SDK until your app calls the OneSignal.consentGiven(true) function. 333 | This is useful if you want to show a Terms and Conditions or privacy popup for GDPR.""" 334 | 335 | data_str = json.dumps({"consent": consent}) 336 | 337 | self.invoke_method( 338 | method_name="consent_required", 339 | arguments={'data': data_str}, 340 | wait_for_result=True, 341 | ) 342 | 343 | async def consent_required_async(self, consent: bool = True) -> None: 344 | """Allows you to completely disable the SDK until your app calls the OneSignal.consentGiven(true) function. 345 | This is useful if you want to show a Terms and Conditions or privacy popup for GDPR.""" 346 | 347 | data_str = json.dumps({"consent": consent}) 348 | 349 | await self.invoke_method_async( 350 | method_name="consent_required", 351 | arguments={'data': data_str}, 352 | wait_for_result=True, 353 | ) 354 | 355 | def request_permission(self, fallback_to_settings: bool = True, wait_timeout: Optional[float] = 25) -> bool: 356 | """Prompt the user for permission to receive push notifications. This will display the native system prompt to 357 | request push notification permission.""" 358 | 359 | data_str = json.dumps({"fallback_to_settings": fallback_to_settings}) 360 | 361 | result = self.invoke_method( 362 | method_name="request_permission", 363 | arguments={'data': data_str}, 364 | wait_for_result=True, 365 | wait_timeout=wait_timeout 366 | ) 367 | 368 | return result == "true" 369 | 370 | async def request_permission_async(self, fallback_to_settings: bool = True, wait_timeout: Optional[float] = 25) -> bool: 371 | """Prompt the user for permission to receive push notifications. This will display the native system prompt to 372 | request push notification permission.""" 373 | 374 | data_str = json.dumps({"fallback_to_settings": fallback_to_settings}) 375 | 376 | result = await self.invoke_method_async( 377 | method_name="request_permission", 378 | arguments={'data': data_str}, 379 | wait_for_result=True, 380 | wait_timeout=wait_timeout 381 | ) 382 | 383 | return result == "true" 384 | 385 | # Métodos para integração com o OneSignal Notifications 386 | def register_for_provisional_authorization(self, wait_timeout: Optional[float] = 25) -> bool: 387 | """Instead of having to prompt the user for permission to send them push notifications, your app can request 388 | provisional authorization.""" 389 | 390 | result = self.invoke_method( 391 | method_name="register_for_provisional_authorization", 392 | wait_for_result=True, 393 | wait_timeout=wait_timeout 394 | ) 395 | return result == "true" 396 | 397 | async def register_for_provisional_authorization_async(self, wait_timeout: Optional[float] = 25) -> bool: 398 | """Instead of having to prompt the user for permission to send them push notifications, your app can request 399 | provisional authorization.""" 400 | 401 | result = await self.invoke_method_async( 402 | method_name="register_for_provisional_authorization", 403 | wait_for_result=True, 404 | wait_timeout=wait_timeout 405 | ) 406 | return result == "true" 407 | 408 | def can_request_permission(self, wait_timeout: Optional[float] = 25) -> bool: 409 | """Whether attempting to request notification permission will show a prompt. Returns true if the device has not 410 | been prompted for push notification permission already.""" 411 | 412 | result = self.invoke_method( 413 | method_name="can_request_permission", 414 | wait_for_result=True, 415 | wait_timeout=wait_timeout 416 | ) 417 | return result == "true" 418 | 419 | async def can_request_permission_async(self, wait_timeout: Optional[float] = 25) -> bool: 420 | """Whether attempting to request notification permission will show a prompt. Returns true if the device has not 421 | been prompted for push notification permission already.""" 422 | 423 | result = await self.invoke_method_async( 424 | method_name="can_request_permission", 425 | wait_for_result=True, 426 | wait_timeout=wait_timeout 427 | ) 428 | return result == "true" 429 | 430 | def opt_in(self) -> None: 431 | """Call this method to receive push notifications on the device or to resume receiving of push notifications 432 | after calling optOut. If needed, this method will prompt the user for push notifications permission.""" 433 | 434 | self.invoke_method( 435 | method_name="opt_in", 436 | wait_for_result=True, 437 | ) 438 | 439 | async def opt_in_async(self) -> None: 440 | """Call this method to receive push notifications on the device or to resume receiving of push notifications 441 | after calling optOut. If needed, this method will prompt the user for push notifications permission.""" 442 | 443 | await self.invoke_method_async( 444 | method_name="opt_in", 445 | wait_for_result=True, 446 | ) 447 | 448 | def opt_out(self) -> None: 449 | """If at any point you want the user to stop receiving push notifications on the current device (regardless of 450 | system-level permission status), you can call this method to opt out.""" 451 | 452 | self.invoke_method( 453 | method_name="opt_in", 454 | wait_for_result=True, 455 | ) 456 | 457 | async def opt_out_async(self) -> None: 458 | """If at any point you want the user to stop receiving push notifications on the current device (regardless of 459 | system-level permission status), you can call this method to opt out.""" 460 | 461 | await self.invoke_method_async( 462 | method_name="opt_in", 463 | wait_for_result=True, 464 | ) 465 | 466 | def prevent_default(self, notification_id: str) -> None: 467 | """The notification willDisplay listener is called whenever a notification arrives and the application is in 468 | foreground""" 469 | 470 | self.invoke_method( 471 | method_name="prevent_default", 472 | arguments={"notification_id": notification_id}, 473 | ) 474 | 475 | @property 476 | def on_notification_opened( 477 | self, 478 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 479 | return self.__on_notification_opened.handler 480 | 481 | @on_notification_opened.setter 482 | def on_notification_opened( 483 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 484 | ): 485 | self.__on_notification_opened.handler = handler 486 | self._set_attr("notification_opened", True if handler is not None else None) 487 | 488 | @property 489 | def on_notification_received( 490 | self, 491 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 492 | return self.__on_notification_received.handler 493 | 494 | @on_notification_received.setter 495 | def on_notification_received( 496 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 497 | ): 498 | self.__on_notification_received.handler = handler 499 | self._set_attr("notification_received", True if handler is not None else None) 500 | 501 | @property 502 | def on_click_in_app_messages( 503 | self, 504 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 505 | return self.__on_click_in_app_messages.handler 506 | 507 | @on_click_in_app_messages.setter 508 | def on_click_in_app_messages( 509 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 510 | ): 511 | self.__on_click_in_app_messages.handler = handler 512 | self._set_attr("click_in_app_messages", True if handler is not None else None) 513 | 514 | @property 515 | def on_will_display_in_app_messages( 516 | self, 517 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 518 | return self.__on_will_display_in_app_messages.handler 519 | 520 | @on_will_display_in_app_messages.setter 521 | def on_will_display_in_app_messages( 522 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 523 | ): 524 | self.__on_will_display_in_app_messages.handler = handler 525 | self._set_attr("will_display_in_app_messages", True if handler is not None else None) 526 | 527 | @property 528 | def on_did_display_in_app_messages( 529 | self, 530 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 531 | return self.__on_did_display_in_app_messages.handler 532 | 533 | @on_did_display_in_app_messages.setter 534 | def on_did_display_in_app_messages( 535 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 536 | ): 537 | self.__on_did_display_in_app_messages.handler = handler 538 | self._set_attr("did_display_in_app_messages", True if handler is not None else None) 539 | 540 | @property 541 | def on_will_dismiss_in_app_messages( 542 | self, 543 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 544 | return self.__on_will_dismiss_in_app_messages.handler 545 | 546 | @on_will_dismiss_in_app_messages.setter 547 | def on_will_dismiss_in_app_messages( 548 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 549 | ): 550 | self.__on_will_dismiss_in_app_messages.handler = handler 551 | self._set_attr("will_dismiss_in_app_messages", True if handler is not None else None) 552 | 553 | @property 554 | def on_did_dismiss_in_app_messages( 555 | self, 556 | ) -> OptionalEventCallable[OneSignalChangeEvent]: 557 | return self.__on_did_dismiss_in_app_messages.handler 558 | 559 | @on_did_dismiss_in_app_messages.setter 560 | def on_did_dismiss_in_app_messages( 561 | self, handler: OptionalEventCallable[OneSignalChangeEvent] 562 | ): 563 | self.__on_did_dismiss_in_app_messages.handler = handler 564 | self._set_attr("did_dismiss_in_app_messages", True if handler is not None else None) 565 | 566 | @property 567 | def on_error(self) -> OptionalControlEventCallable: 568 | return self._get_attr("error") 569 | 570 | @on_error.setter 571 | def on_error(self, handler: OptionalControlEventCallable): 572 | self._add_event_handler("error", handler) 573 | --------------------------------------------------------------------------------