diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml
index 29ebfbb351..3265ac214f 100644
--- a/client/android/app/src/main/AndroidManifest.xml
+++ b/client/android/app/src/main/AndroidManifest.xml
@@ -15,6 +15,10 @@
+
+
+
+
diff --git a/client/ios/Podfile b/client/ios/Podfile
index 279576f388..301860ff43 100644
--- a/client/ios/Podfile
+++ b/client/ios/Podfile
@@ -35,7 +35,17 @@ target 'Runner' do
end
post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings target
+ end
+
+ ################ Awesome Notifications pod modification ###################
+ awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
+ require awesome_pod_file
+ update_awesome_pod_build_settings(installer)
end
-end
+
+ awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
+ require awesome_pod_file
+ update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root)
+ ################ Awesome Notifications pod modification ###################
\ No newline at end of file
diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock
index 3686d0c2a1..e9725bb850 100644
--- a/client/ios/Podfile.lock
+++ b/client/ios/Podfile.lock
@@ -1,6 +1,11 @@
PODS:
- audioplayers_darwin (0.0.1):
- Flutter
+ - awesome_notifications (0.10.0):
+ - Flutter
+ - IosAwnCore (~> 0.10.0)
+ - device_info_plus (0.0.1):
+ - Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
@@ -47,6 +52,7 @@ PODS:
- GoogleUserMessagingPlatform (2.7.0)
- integration_test (0.0.1):
- Flutter
+ - IosAwnCore (0.10.0)
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_native_event_loop (1.0.0):
@@ -90,6 +96,8 @@ PODS:
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
+ - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`)
+ - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
@@ -118,12 +126,17 @@ SPEC REPOS:
- DKPhotoGallery
- Google-Mobile-Ads-SDK
- GoogleUserMessagingPlatform
+ - IosAwnCore
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/ios"
+ awesome_notifications:
+ :path: ".symlinks/plugins/awesome_notifications/ios"
+ device_info_plus:
+ :path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
@@ -168,35 +181,38 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
- audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
+ audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab
+ awesome_notifications: 0f432b28098d193920b11a44cfa9d2d9313a3888
+ device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
- file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
+ file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
- geolocator_apple: 6cbaf322953988e009e5ecb481f07efece75c450
+ geolocator_apple: d981750b9f47dbdb02427e1476d9a04397beb8d9
Google-Mobile-Ads-SDK: 13e6e98edfd78ad8d8a791edb927658cc260a56f
- google_mobile_ads: 2a538d8e42b1813809782792e48f8cf4374c2180
+ google_mobile_ads: dc2b2a5884bef7ab2b4ff30022a513df5373e208
GoogleUserMessagingPlatform: a8b56893477f67212fbc8411c139e61d463349f5
- integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
- media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
- media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
- media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
- package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
- path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
- permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
- record_darwin: df0a677188e5fed18472550298e675f19ddaffbe
- rive_common: c537b4eed761e903a9403d93c347b69bd7a4762f
- screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
+ integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
+ IosAwnCore: 653786a911089012092ce831f2945cd339855a89
+ media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
+ media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf
+ media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
+ package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
+ path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
+ permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
+ record_darwin: 1630616226de4038fa17cec21b11403ca510ec3e
+ rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
+ screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
- sensors_plus: 42b9de1b8237675fa8d8121e4bb93be0f79fa61d
- shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
+ sensors_plus: 1c5f0a01ce21c609a4df404c4e6879d62bce287f
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
- torch_light: 682062fa12102172fa38b6b14c106d93b060f83e
- url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
- volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
- wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
- webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
+ torch_light: d093d579a221a59ef8a6b8c0eca20d52f7178087
+ url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
+ volume_controller: ca1cde542ee70fad77d388f82e9616488110942b
+ wakelock_plus: fd58c82b1388f4afe3fe8aa2c856503a262a5b03
+ webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c
-PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011
+PODFILE CHECKSUM: f72fa9a23cb5aeeaa7768cc7b4b325a6cffa1f2a
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.2
diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj
index 51ef87bc74..87227d4e4b 100644
--- a/client/ios/Runner.xcodeproj/project.pbxproj
+++ b/client/ios/Runner.xcodeproj/project.pbxproj
@@ -373,7 +373,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
@@ -505,7 +507,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
@@ -531,7 +535,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
diff --git a/client/ios/Runner/AppDelegate.swift b/client/ios/Runner/AppDelegate.swift
index b636303481..368b2aa683 100644
--- a/client/ios/Runner/AppDelegate.swift
+++ b/client/ios/Runner/AppDelegate.swift
@@ -1,5 +1,7 @@
import UIKit
import Flutter
+import awesome_notifications
+import shared_preferences_foundation
@main
@objc class AppDelegate: FlutterAppDelegate {
@@ -7,7 +9,17 @@ import Flutter
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
+
GeneratedPluginRegistrant.register(with: self)
+
+ // Register AwesomeNotifications Plugin for background actions
+ SwiftAwesomeNotificationsPlugin.setPluginRegistrantCallback { registry in
+ SwiftAwesomeNotificationsPlugin.register(
+ with: registry.registrar(forPlugin: "io.flutter.plugins.awesomenotifications.AwesomeNotificationsPlugin")!)
+ SharedPreferencesPlugin.register(
+ with: registry.registrar(forPlugin: "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")!)
+ }
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
diff --git a/client/lib/main.dart b/client/lib/main.dart
index 4f2f5e0afe..3f4a184d11 100644
--- a/client/lib/main.dart
+++ b/client/lib/main.dart
@@ -17,6 +17,7 @@ import 'package:flet_rive/flet_rive.dart' as flet_rive;
// --FAT_CLIENT_START--
import 'package:flet_video/flet_video.dart' as flet_video;
// --FAT_CLIENT_END--
+import 'package:flet_notifications/flet_notifications.dart' as flet_notifications;
import 'package:flet_webview/flet_webview.dart' as flet_webview;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -47,6 +48,7 @@ void main([List? args]) async {
flet_rive.ensureInitialized();
flet_webview.ensureInitialized();
flet_flashlight.ensureInitialized();
+ flet_notifications.ensureInitialized();
var pageUrl = Uri.base.toString();
var assetsDir = "";
@@ -117,6 +119,7 @@ void main([List? args]) async {
flet_rive.createControl,
flet_webview.createControl,
flet_flashlight.createControl,
+ flet_notifications.createControl,
],
));
}
diff --git a/client/linux/flutter/generated_plugin_registrant.cc b/client/linux/flutter/generated_plugin_registrant.cc
index ce73c487f9..0afc8bba69 100644
--- a/client/linux/flutter/generated_plugin_registrant.cc
+++ b/client/linux/flutter/generated_plugin_registrant.cc
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include
+#include
#include
#include
#include
@@ -20,6 +21,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
+ g_autoptr(FlPluginRegistrar) awesome_notifications_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "AwesomeNotificationsPlugin");
+ awesome_notifications_plugin_register_with_registrar(awesome_notifications_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
diff --git a/client/linux/flutter/generated_plugins.cmake b/client/linux/flutter/generated_plugins.cmake
index db550dddca..6348ae91e8 100644
--- a/client/linux/flutter/generated_plugins.cmake
+++ b/client/linux/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
+ awesome_notifications
media_kit_libs_linux
media_kit_video
record_linux
diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift
index 79ace66a63..1eea9d0b35 100644
--- a/client/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation
import audioplayers_darwin
+import awesome_notifications
import device_info_plus
import geolocator_apple
import media_kit_libs_macos_video
@@ -25,6 +26,7 @@ import window_to_front
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
+ AwesomeNotificationsPlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
diff --git a/client/pubspec.lock b/client/pubspec.lock
index 703fccc576..81b596fa5f 100644
--- a/client/pubspec.lock
+++ b/client/pubspec.lock
@@ -81,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
+ awesome_notifications:
+ dependency: transitive
+ description:
+ name: awesome_notifications
+ sha256: d051ffb694a53da216ff13d02c8ec645d75320048262f7e6b3c1d95a4f54c902
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.0"
boolean_selector:
dependency: transitive
description:
@@ -263,7 +271,7 @@ packages:
path: "../packages/flet"
relative: true
source: path
- version: "0.27.1"
+ version: "0.27.4"
flet_ads:
dependency: "direct main"
description:
@@ -327,6 +335,13 @@ packages:
url: "https://github.com/flet-dev/flet-map.git"
source: git
version: "0.1.0"
+ flet_notifications:
+ dependency: "direct main"
+ description:
+ path: "../packages/flet_notifications"
+ relative: true
+ source: path
+ version: "0.1.0"
flet_permission_handler:
dependency: "direct main"
description:
diff --git a/client/pubspec.yaml b/client/pubspec.yaml
index c6f003aba9..d0f8d6ec1c 100644
--- a/client/pubspec.yaml
+++ b/client/pubspec.yaml
@@ -93,6 +93,8 @@ dependencies:
url: https://github.com/flet-dev/flet-flashlight.git
ref: 0.1.0
path: src/flutter/flet_flashlight
+ flet_notifications:
+ path: ../packages/flet_notifications
url_strategy: ^0.2.0
cupertino_icons: ^1.0.6
diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc
index be1578b395..0889f5d987 100644
--- a/client/windows/flutter/generated_plugin_registrant.cc
+++ b/client/windows/flutter/generated_plugin_registrant.cc
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include
+#include
#include
#include
#include
@@ -22,6 +23,8 @@
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
+ AwesomeNotificationsPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake
index 61e94283e8..76d0015841 100644
--- a/client/windows/flutter/generated_plugins.cmake
+++ b/client/windows/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
+ awesome_notifications
geolocator_windows
media_kit_libs_windows_video
media_kit_video
diff --git a/packages/flet_notifications/.gitignore b/packages/flet_notifications/.gitignore
new file mode 100644
index 0000000000..e050eb5129
--- /dev/null
+++ b/packages/flet_notifications/.gitignore
@@ -0,0 +1,31 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/
+.flutter-plugins
+.flutter-plugins-dependencies
\ No newline at end of file
diff --git a/packages/flet_notifications/.metadata b/packages/flet_notifications/.metadata
new file mode 100644
index 0000000000..07d8623a38
--- /dev/null
+++ b/packages/flet_notifications/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829"
+ channel: "stable"
+
+project_type: package
diff --git a/packages/flet_notifications/CHANGELOG.md b/packages/flet_notifications/CHANGELOG.md
new file mode 100644
index 0000000000..2f5af7e43b
--- /dev/null
+++ b/packages/flet_notifications/CHANGELOG.md
@@ -0,0 +1,3 @@
+# 0.1.0
+
+Initial release of the package.
\ No newline at end of file
diff --git a/packages/flet_notifications/LICENSE b/packages/flet_notifications/LICENSE
new file mode 100644
index 0000000000..f49a4e16e6
--- /dev/null
+++ b/packages/flet_notifications/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/packages/flet_notifications/README.md b/packages/flet_notifications/README.md
new file mode 100644
index 0000000000..5e948f46d5
--- /dev/null
+++ b/packages/flet_notifications/README.md
@@ -0,0 +1,3 @@
+# Flet `Notifications` control
+
+`Notifications` control to use in Flet apps.
\ No newline at end of file
diff --git a/packages/flet_notifications/analysis_options.yaml b/packages/flet_notifications/analysis_options.yaml
new file mode 100644
index 0000000000..a5744c1cfb
--- /dev/null
+++ b/packages/flet_notifications/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/packages/flet_notifications/lib/flet_notifications.dart b/packages/flet_notifications/lib/flet_notifications.dart
new file mode 100644
index 0000000000..3b019b6ff7
--- /dev/null
+++ b/packages/flet_notifications/lib/flet_notifications.dart
@@ -0,0 +1,3 @@
+library flet_lottie;
+
+export "src/create_control.dart" show createControl, ensureInitialized;
diff --git a/packages/flet_notifications/lib/src/create_control.dart b/packages/flet_notifications/lib/src/create_control.dart
new file mode 100644
index 0000000000..e6ea9103e2
--- /dev/null
+++ b/packages/flet_notifications/lib/src/create_control.dart
@@ -0,0 +1,23 @@
+import 'package:awesome_notifications/awesome_notifications.dart';
+import 'package:flet/flet.dart';
+import 'package:flet_notifications/src/service.dart';
+import 'package:flutter/material.dart';
+
+import 'notifications.dart';
+
+CreateControlFactory createControl = (CreateControlArgs args) {
+ switch (args.control.type) {
+ case "notifications":
+ return NotificationControl(
+ parent: args.parent,
+ control: args.control,
+ nextChild: args.nextChild,
+ backend: args.backend);
+ default:
+ return null;
+ }
+};
+
+void ensureInitialized() async {
+ // initialization is done in NotificationControl.initState
+}
\ No newline at end of file
diff --git a/packages/flet_notifications/lib/src/notifications.dart b/packages/flet_notifications/lib/src/notifications.dart
new file mode 100644
index 0000000000..00191f54fc
--- /dev/null
+++ b/packages/flet_notifications/lib/src/notifications.dart
@@ -0,0 +1,231 @@
+import 'dart:convert';
+
+import 'package:awesome_notifications/awesome_notifications.dart';
+import 'package:flet/flet.dart';
+import 'package:flet_notifications/src/utils/notifications.dart';
+import 'package:flutter/material.dart';
+
+import 'service.dart';
+
+class NotificationControl extends StatefulWidget {
+ final Control? parent;
+ final Control control;
+ final Widget? nextChild;
+ final FletControlBackend backend;
+
+ const NotificationControl(
+ {super.key,
+ required this.parent,
+ required this.control,
+ required this.nextChild,
+ required this.backend});
+
+ @override
+ State createState() => _NotificationControlState();
+}
+
+class _NotificationControlState extends State
+ with FletStoreMixin {
+ @override
+ void initState() {
+ super.initState();
+ Future.microtask(() => _initializeService());
+ }
+
+ Future _initializeService() async {
+ var channels = parseNotificationChannels(
+ widget.control, "channels", Theme.of(context));
+ await NotificationService.initializeLocalNotifications(
+ channels: channels,
+ languageCode: widget.control.attrString("languageCode"),
+ icon: widget.control.attrString("icon"),
+ );
+ }
+
+ @override
+ void dispose() {
+ AwesomeNotifications().dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint(
+ "Notifications build: ${widget.control.id} (${widget.control.hashCode})");
+
+ () async {
+ widget.backend.subscribeMethods(widget.control.id,
+ (methodName, args) async {
+ switch (methodName) {
+ case "show":
+ var content =
+ notificationContentFromJSON(Theme.of(context), args["content"]);
+ var actionButtons = notificationActionButtonsFromJSON(
+ Theme.of(context), args["action_buttons"]);
+ var schedule = args["schedule"] != null
+ ? args["schedule_parser"] == "interval"
+ ? notificationIntervalFromJSON(args["schedule"],
+ jsonDecode: true)
+ : notificationCalendarFromJSON(args["schedule"],
+ jsonDecode: true)
+ : null;
+ if (content != null) {
+ debugPrint("NotificationService.showNotification");
+ NotificationService.showNotification(content,
+ actionButtons: actionButtons, schedule: schedule);
+ }
+ // dismissals
+ case "dismiss":
+ var id = parseInt(args["id"]);
+ var channelKey = args["channel_key"];
+ var groupKey = args["group_key"];
+ if (id != null) {
+ await AwesomeNotifications().dismiss(id);
+ } else if (channelKey != null) {
+ await AwesomeNotifications()
+ .dismissNotificationsByChannelKey(channelKey);
+ } else if (groupKey != null) {
+ await AwesomeNotifications()
+ .dismissNotificationsByGroupKey(groupKey);
+ }
+ break;
+ case "dismiss_all":
+ await AwesomeNotifications().dismissAllNotifications();
+
+ // cancellations
+ case "cancel":
+ var id = parseInt(args["id"]);
+ var channelKey = args["channel_key"];
+ var groupKey = args["group_key"];
+ if (id != null) {
+ await AwesomeNotifications().cancelSchedule(id);
+ } else if (channelKey != null) {
+ await AwesomeNotifications()
+ .cancelNotificationsByChannelKey(channelKey);
+ } else if (groupKey != null) {
+ await AwesomeNotifications()
+ .cancelNotificationsByGroupKey(groupKey);
+ }
+ break;
+ case "cancel_schedule":
+ var id = parseInt(args["id"]);
+ var channelKey = args["channel_key"];
+ var groupKey = args["group_key"];
+ if (id != null) {
+ await AwesomeNotifications().cancelSchedule(id);
+ } else if (channelKey != null) {
+ await AwesomeNotifications()
+ .cancelSchedulesByChannelKey(channelKey);
+ } else if (groupKey != null) {
+ await AwesomeNotifications().cancelSchedulesByGroupKey(groupKey);
+ }
+ break;
+ case "cancel_all_schedules":
+ await AwesomeNotifications().cancelAllSchedules();
+ break;
+
+ // badge_counter
+ case "get_badge_counter":
+ return await AwesomeNotifications()
+ .getGlobalBadgeCounter()
+ .then((value) => value.toString());
+ case "set_badge_counter":
+ var value = parseInt(args["value"]);
+ if (value != null) {
+ await AwesomeNotifications().setGlobalBadgeCounter(value);
+ }
+ break;
+ case "increment_badge_counter":
+ return await AwesomeNotifications()
+ .incrementGlobalBadgeCounter()
+ .then((value) => value.toString());
+ case "decrement_badge_counter":
+ return await AwesomeNotifications()
+ .decrementGlobalBadgeCounter()
+ .then((value) => value.toString());
+ case "reset_badge_counter":
+ await AwesomeNotifications().resetGlobalBadge();
+ break;
+
+ // channels
+ case "set_channel":
+ var notificationChannel = notificationChannelFromJSON(
+ Theme.of(context), args["channel"],
+ jsonDecode: true);
+ if (notificationChannel != null) {
+ await AwesomeNotifications().setChannel(notificationChannel,
+ forceUpdate: parseBool(args["force_update"], false)!);
+ }
+ break;
+ case "remove_channel":
+ var channelKey = args["channel_key"];
+ if (channelKey != null) {
+ await AwesomeNotifications().removeChannel(channelKey);
+ }
+ break;
+
+ // permissions
+ case "is_allowed":
+ return await AwesomeNotifications()
+ .isNotificationAllowed()
+ .then((value) => value.toString());
+ case "request_permission":
+ return await AwesomeNotifications()
+ .requestPermissionToSendNotifications()
+ .then((value) => value.toString());
+
+ // time
+ case "get_local_timezone_identifier":
+ return await AwesomeNotifications()
+ .getLocalTimeZoneIdentifier()
+ .then((value) => value);
+ case "get_next_date":
+ return "";
+ case "get_utc_timezone_identifier":
+ return await AwesomeNotifications()
+ .getUtcTimeZoneIdentifier()
+ .then((value) => value);
+
+ // others
+ case "show_alarm_page":
+ await AwesomeNotifications().showAlarmPage();
+ break;
+ case "get_initial_action":
+ return await AwesomeNotifications()
+ .getInitialNotificationAction(
+ removeFromActionEvents:
+ parseBool(args["remove_from_action_events"], false)!)
+ .then((ReceivedAction? action) => jsonEncode(action?.toMap()));
+ case "show_global_dnd_override_page":
+ await AwesomeNotifications().showGlobalDndOverridePage();
+ break;
+
+ case "get_lifecycle":
+ return await AwesomeNotifications()
+ .getAppLifeCycle()
+ .then((value) => value.name.toLowerCase());
+ case "get_localization":
+ return await AwesomeNotifications()
+ .getLocalization()
+ .then((value) => value);
+ case "is_active_on_status_bar":
+ var id = parseInt(args["id"]);
+ if (id != null) {
+ return await AwesomeNotifications()
+ .isNotificationActiveOnStatusBar(id: id)
+ .then((value) => value.toString());
+ }
+ break;
+
+ case "get_ids_active_on_status_bar":
+ return await AwesomeNotifications()
+ .getAllActiveNotificationIdsOnStatusBar()
+ .then((value) => jsonEncode(value));
+ }
+ return null;
+ });
+ }();
+
+ return const SizedBox.shrink();
+ }
+}
diff --git a/packages/flet_notifications/lib/src/service.dart b/packages/flet_notifications/lib/src/service.dart
new file mode 100644
index 0000000000..723d5a5463
--- /dev/null
+++ b/packages/flet_notifications/lib/src/service.dart
@@ -0,0 +1,46 @@
+import 'package:awesome_notifications/awesome_notifications.dart';
+import 'package:flutter/material.dart';
+
+class NotificationService {
+ static Future initializeLocalNotifications(
+ {required List channels, String? languageCode, String? icon}) async {
+ await AwesomeNotifications().initialize(
+ icon,
+ channels,
+ languageCode: languageCode,
+ debug: true);
+
+ await AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
+ if (!isAllowed) {
+ AwesomeNotifications().requestPermissionToSendNotifications();
+ }
+ });
+
+ await AwesomeNotifications().setListeners(
+ onActionReceivedMethod: onActionReceivedMethod,
+ onDismissActionReceivedMethod: (receivedNotification) async {
+ debugPrint('Notification dismissed: ${receivedNotification.id}');
+ },
+ onNotificationDisplayedMethod: (receivedNotification) async {
+ debugPrint('Notification displayed: ${receivedNotification.id}');
+ },
+ onNotificationCreatedMethod: (receivedNotification) async {
+ debugPrint('Notification created: ${receivedNotification.id}');
+ },
+ );
+ }
+
+ static Future onActionReceivedMethod(
+ ReceivedAction receivedAction) async {
+ debugPrint('Notification Action received');
+ }
+
+ static void showNotification(NotificationContent content,
+ {List? actionButtons, NotificationSchedule? schedule}) {
+ AwesomeNotifications().createNotification(
+ content: content,
+ actionButtons: actionButtons,
+ schedule: schedule,
+ );
+ }
+}
diff --git a/packages/flet_notifications/lib/src/utils/notifications.dart b/packages/flet_notifications/lib/src/utils/notifications.dart
new file mode 100644
index 0000000000..f105745311
--- /dev/null
+++ b/packages/flet_notifications/lib/src/utils/notifications.dart
@@ -0,0 +1,352 @@
+import 'dart:convert';
+
+import 'package:awesome_notifications/awesome_notifications.dart';
+import 'package:flet/flet.dart';
+import 'package:flutter/material.dart';
+
+NotificationContent? parseNotificationContent(
+ Control control, String propName, ThemeData theme,
+ [NotificationContent? defValue]) {
+ var v = control.attrString(propName);
+ if (v == null) {
+ return defValue;
+ }
+ final j1 = json.decode(v);
+ return notificationContentFromJSON(j1, theme, defValue);
+}
+
+NotificationContent? notificationContentFromJSON(ThemeData theme, dynamic j,
+ [NotificationContent? defValue]) {
+ j = j != null ? json.decode(j) : null;
+ var id = parseInt(j['id']);
+ var channelKey = j['channel_key'];
+
+ if (j == null || id == null || channelKey == null) {
+ return defValue;
+ }
+
+ return NotificationContent(
+ id: id,
+ channelKey: channelKey,
+ title: j["title"],
+ body: j["body"],
+ titleLocKey: j["title_loc_key"],
+ bodyLocKey: j["body_loc_key"],
+ //titleLocArgs: ,
+ //bodyLocArgs:,
+ groupKey: j["group_key"],
+ summary: j["summary"],
+ icon: j["icon"],
+ largeIcon: j["large_icon"],
+ bigPicture: j["big_picture"],
+ customSound: j["custom_sound"],
+ showWhen: parseBool(j["show_when"], true)!,
+ wakeUpScreen: parseBool(j["wake_up_screen"], false)!,
+ fullScreenIntent: parseBool(j["full_screen_intent"], false)!,
+ criticalAlert: parseBool(j["critical_alert"], false)!,
+ roundedLargeIcon: parseBool(j["rounded_large_icon"], false)!,
+ roundedBigPicture: parseBool(j["rounded_big_picture"], false)!,
+ autoDismissible: parseBool(j["auto_dismissible"], true)!,
+ color: parseColor(theme, j["color"]),
+ timeoutAfter: durationFromJSON(j["timeout_after"]),
+ chronometer: durationFromJSON(j["chronometer"]),
+ backgroundColor: parseColor(theme, j["bgcolor"]),
+ hideLargeIconOnExpand: parseBool(j["hide_large_icon_on_expand"], false)!,
+ locked: parseBool(j["locked"], false)!,
+ progress: parseDouble(j["progress"]),
+ badge: parseInt(j["badge"]),
+ ticker: j["ticker"],
+ displayOnForeground: parseBool(j["display_on_foreground"], true)!,
+ displayOnBackground: parseBool(j["display_on_background"], true)!,
+ duration: durationFromJSON(j["duration"]),
+ playbackSpeed: parseDouble(j["playback_speed"]),
+ actionType: parseActionType(j["action_type"], ActionType.Default)!,
+ category: parseNotificationCategory(j["category"]),
+ notificationLayout:
+ parseNotificationLayout(j["layout"], NotificationLayout.Default)!,
+ );
+}
+
+NotificationActionButton? notificationActionButtonFromJSON(
+ ThemeData theme, dynamic j,
+ [NotificationActionButton? defValue]) {
+ var key = j?['key'];
+ var label = j?['label'];
+
+ if (j == null || key == null || label == null) {
+ return defValue;
+ }
+
+ return NotificationActionButton(
+ key: key,
+ label: label,
+ enabled: !parseBool(j["disabled"], false)!,
+ isAuthenticationRequired: parseBool(j["requires_authentication"], false)!,
+ isDangerousOption: parseBool(j["dangerous"], false)!,
+ requireInputText: parseBool(j["require_text_input"], false)!,
+ showInCompactView: parseBool(j["show_in_compact_view"], true)!,
+ autoDismissible: parseBool(j["auto_dismissible"], true)!,
+ color: parseColor(theme, j["color"]),
+ icon: j["icon"],
+ actionType: parseActionType(j["action_type"], ActionType.Default)!,
+ );
+}
+
+List? notificationActionButtonsFromJSON(
+ ThemeData theme, dynamic j,
+ [List? defValue]) {
+ j = j != null ? json.decode(j) : null;
+ if (j == null) return defValue;
+
+ var buttons = [];
+ for (var b in j) {
+ var actionButton = notificationActionButtonFromJSON(theme, b);
+ if (actionButton != null) {
+ buttons.add(actionButton);
+ }
+ }
+ return buttons;
+}
+
+List parseNotificationChannels(
+ Control control, String propName, ThemeData theme,
+ [List? defValue]) {
+ var v = control.attrString(propName);
+ if (v == null) {
+ return defValue ?? [];
+ }
+
+ final List jsonList = json.decode(v);
+ if (jsonList.isEmpty) {
+ return defValue ?? [];
+ }
+
+ List channels = [];
+ for (var j in jsonList) {
+ var channel = notificationChannelFromJSON(theme, j);
+ if (channel != null) {
+ channels.add(channel);
+ }
+ }
+
+ return channels;
+}
+
+NotificationChannel? notificationChannelFromJSON(ThemeData theme, dynamic j,
+ {NotificationChannel? defValue, bool jsonDecode = false}) {
+ if (jsonDecode && j is String) {
+ j = json.decode(j);
+ }
+ var channelKey = j?['channel_key'];
+ var channelName = j?['channel_name'];
+ var channelDescription = j?['channel_description'];
+
+ if (j == null ||
+ channelKey == null ||
+ channelName == null ||
+ channelDescription == null) {
+ return defValue;
+ }
+
+ return NotificationChannel(
+ channelKey: channelKey,
+ channelName: channelName,
+ channelDescription: channelDescription,
+ channelGroupKey: j["channel_group_key"],
+ channelShowBadge: parseBool(j["channel_show_badge"]),
+ criticalAlerts: parseBool(j["critical_alerts"]),
+ defaultColor: parseColor(theme, j["default_color"]),
+ enableLights: parseBool(j["enable_lights"]),
+ enableVibration: parseBool(j["enable_vibration"]),
+ ledColor: parseColor(theme, j["led_color"]),
+ ledOnMs: parseInt(j["led_on_ms"]),
+ ledOffMs: parseInt(j["led_off_ms"]),
+ onlyAlertOnce: parseBool(j["only_alert_once"]),
+ playSound: parseBool(j["play_sound"]),
+ soundSource: j["sound_source"],
+ groupKey: j["group_key"],
+ icon: j["icon"],
+ locked: parseBool(j["locked"], false)!,
+ defaultPrivacy: parseNotificationPrivacy(j["privacy"]),
+ groupSort: parseGroupSort(j["group_sort"]),
+ importance: parseNotificationImportance(j["importance"]),
+ defaultRingtoneType: parseRingtoneType(j["ringtone_type"]),
+ groupAlertBehavior: parseGroupAlertBehavior(j["group_alert_behavior"]),
+ );
+}
+
+NotificationCalendar? notificationCalendarFromJSON(dynamic j,
+ {NotificationCalendar? defValue, bool jsonDecode = false}) {
+ if (jsonDecode && j is String) {
+ j = json.decode(j);
+ }
+
+ if (j == null) {
+ return defValue;
+ }
+
+ return NotificationCalendar(
+ allowWhileIdle: parseBool(j["allow_while_idle"], false)!,
+ preciseAlarm: parseBool(j["precise_alarm"], false)!,
+ repeats: parseBool(j["repeats"], false)!,
+ timeZone: j["time_zone"],
+ day: parseInt(j["day"]),
+ hour: parseInt(j["hour"]),
+ minute: parseInt(j["minute"]),
+ second: parseInt(j["second"]),
+ millisecond: parseInt(j["millisecond"]),
+ month: parseInt(j["month"]),
+ weekday: parseInt(j["weekday"]),
+ weekOfYear: parseInt(j["week_of_year"]),
+ year: parseInt(j["year"]),
+ era: parseInt(j["era"]),
+ // weekOfMonth (not fully implemented atm)
+ );
+}
+
+NotificationInterval? notificationIntervalFromJSON(dynamic j,
+ {NotificationInterval? defValue, bool jsonDecode = false}) {
+ if (jsonDecode && j is String) {
+ j = json.decode(j);
+ }
+
+ if (j == null || j["interval"] == null) {
+ return defValue;
+ }
+
+ return NotificationInterval(
+ interval: durationFromJSON(j["interval"]),
+ allowWhileIdle: parseBool(j["allow_while_idle"], false)!,
+ preciseAlarm: parseBool(j["precise_alarm"], false)!,
+ repeats: parseBool(j["repeats"], false)!,
+ timeZone: j["time_zone"],
+ );
+}
+
+ActionType? parseActionType(String? value, [ActionType? defaultActionType]) {
+ if (value == null) {
+ return defaultActionType;
+ }
+ const Map actionMap = {
+ 'default': ActionType.Default,
+ 'disabled': ActionType.DisabledAction,
+ 'keepontop': ActionType.KeepOnTop,
+ 'silent': ActionType.SilentAction,
+ 'silentbackground': ActionType.SilentBackgroundAction,
+ 'dismiss': ActionType.DismissAction,
+ };
+ return actionMap[value.toLowerCase()] ?? defaultActionType;
+}
+
+NotificationCategory? parseNotificationCategory(String? value,
+ [NotificationCategory? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map categoryMap = {
+ 'alarm': NotificationCategory.Alarm,
+ 'call': NotificationCategory.Call,
+ 'email': NotificationCategory.Email,
+ 'error': NotificationCategory.Error,
+ 'event': NotificationCategory.Event,
+ 'localsharing': NotificationCategory.LocalSharing,
+ 'message': NotificationCategory.Message,
+ 'missedcall': NotificationCategory.MissedCall,
+ 'navigation': NotificationCategory.Navigation,
+ 'progress': NotificationCategory.Progress,
+ 'promo': NotificationCategory.Promo,
+ 'recommendation': NotificationCategory.Recommendation,
+ 'reminder': NotificationCategory.Reminder,
+ 'service': NotificationCategory.Service,
+ 'social': NotificationCategory.Social,
+ 'status': NotificationCategory.Status,
+ 'stopwatch': NotificationCategory.StopWatch,
+ 'transport': NotificationCategory.Transport,
+ 'workout': NotificationCategory.Workout,
+ };
+ return categoryMap[value.toLowerCase()] ?? defValue;
+}
+
+NotificationLayout? parseNotificationLayout(String? value,
+ [NotificationLayout? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map layoutMap = {
+ 'default': NotificationLayout.Default,
+ 'bigpicture': NotificationLayout.BigPicture,
+ 'bigtext': NotificationLayout.BigText,
+ 'inbox': NotificationLayout.Inbox,
+ 'progressbar': NotificationLayout.ProgressBar,
+ 'messaging': NotificationLayout.Messaging,
+ 'messaginggroup': NotificationLayout.MessagingGroup,
+ 'mediaplayer': NotificationLayout.MediaPlayer,
+ };
+ return layoutMap[value.toLowerCase()] ?? defValue;
+}
+
+NotificationPrivacy? parseNotificationPrivacy(String? value,
+ [NotificationPrivacy? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map privacyMap = {
+ 'secret': NotificationPrivacy.Secret,
+ 'private': NotificationPrivacy.Private,
+ 'public': NotificationPrivacy.Public,
+ };
+ return privacyMap[value.toLowerCase()] ?? defValue;
+}
+
+GroupSort? parseGroupSort(String? value, [GroupSort? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map groupSortMap = {
+ 'ascending': GroupSort.Asc,
+ 'descending': GroupSort.Desc,
+ };
+ return groupSortMap[value.toLowerCase()] ?? defValue;
+}
+
+NotificationImportance? parseNotificationImportance(String? value,
+ [NotificationImportance? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map importanceMap = {
+ 'none': NotificationImportance.None,
+ 'default': NotificationImportance.Default,
+ 'max': NotificationImportance.Max,
+ 'minimum': NotificationImportance.Min,
+ 'high': NotificationImportance.High,
+ 'low': NotificationImportance.Low,
+ };
+ return importanceMap[value.toLowerCase()] ?? defValue;
+}
+
+DefaultRingtoneType? parseRingtoneType(String? value,
+ [DefaultRingtoneType? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map ringtoneMap = {
+ 'alarm': DefaultRingtoneType.Alarm,
+ 'notification': DefaultRingtoneType.Notification,
+ 'ringtone': DefaultRingtoneType.Ringtone,
+ };
+ return ringtoneMap[value.toLowerCase()] ?? defValue;
+}
+
+GroupAlertBehavior? parseGroupAlertBehavior(String? value,
+ [GroupAlertBehavior? defValue]) {
+ if (value == null) {
+ return defValue;
+ }
+ const Map behaviorMap = {
+ 'all': GroupAlertBehavior.All,
+ 'summary': GroupAlertBehavior.Summary,
+ 'children': GroupAlertBehavior.Children,
+ };
+ return behaviorMap[value.toLowerCase()] ?? defValue;
+}
diff --git a/packages/flet_notifications/pubspec.yaml b/packages/flet_notifications/pubspec.yaml
new file mode 100644
index 0000000000..dea18ac8da
--- /dev/null
+++ b/packages/flet_notifications/pubspec.yaml
@@ -0,0 +1,27 @@
+name: flet_notifications
+description: Flet Notifications control
+homepage: https://flet.dev
+repository: https://github.com/flet-dev/flet-notifications/src/flutter/flet_notifications
+version: 0.1.0
+
+environment:
+ sdk: '>=3.2.3 <4.0.0'
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ collection: ^1.16.0
+ awesome_notifications: ^0.10.0
+
+ flet:
+ path: ../flet
+
+
+
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.0
\ No newline at end of file
diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py
index 8df93d4808..6253cb85a7 100644
--- a/sdk/python/packages/flet/src/flet/__init__.py
+++ b/sdk/python/packages/flet/src/flet/__init__.py
@@ -24,6 +24,23 @@
from flet.core.animated_switcher import AnimatedSwitcher, AnimatedSwitcherTransition
from flet.core.animation import Animation, AnimationCurve
from flet.core.app_bar import AppBar
+from flet.core.notifications import (
+ Notifications,
+ NotificationContent,
+ NotificationActionButton,
+ NotificationActionType,
+ NotificationChannel,
+ NotificationCategory,
+ NotificationPrivacy,
+ NotificationGroupAlertBehavior,
+ NotificationGroupSort,
+ NotificationImportance,
+ NotificationRingtoneType,
+ NotificationLayout,
+ NotificationLifeCycle,
+ NotificationCalendar,
+ NotificationInterval,
+)
from flet.core.audio import (
Audio,
AudioDurationChangeEvent,
diff --git a/sdk/python/packages/flet/src/flet/core/control.py b/sdk/python/packages/flet/src/flet/core/control.py
index 434d6a5e79..06e46d0d72 100644
--- a/sdk/python/packages/flet/src/flet/core/control.py
+++ b/sdk/python/packages/flet/src/flet/core/control.py
@@ -345,7 +345,10 @@ def invoke_method(
arguments: Optional[Dict[str, str]] = None,
wait_for_result: bool = False,
wait_timeout: Optional[float] = 5,
- ) -> Optional[str]:
+ result_type: Literal[
+ "string", "int", "float", "bool", "json_encoded"
+ ] = "string",
+ ) -> Optional[Any]:
assert (
self.__page
), f"{self.__class__.__qualname__} Control must be added to the page first"
@@ -358,6 +361,7 @@ def invoke_method(
arguments=arguments,
wait_for_result=wait_for_result,
wait_timeout=wait_timeout,
+ result_type=result_type,
)
def invoke_method_async(
@@ -366,7 +370,10 @@ def invoke_method_async(
arguments: Optional[Dict[str, str]] = None,
wait_for_result: bool = False,
wait_timeout: Optional[float] = 5,
- ):
+ result_type: Literal[
+ "string", "int", "float", "bool", "json_encoded"
+ ] = "string",
+ ) -> Optional[Any]:
assert (
self.__page
), f"{self.__class__.__qualname__} Control must be added to the page first"
@@ -379,6 +386,7 @@ def invoke_method_async(
arguments=arguments,
wait_for_result=wait_for_result,
wait_timeout=wait_timeout,
+ result_type=result_type,
)
def copy_attrs(self, dest: Dict[str, Any]) -> None:
diff --git a/sdk/python/packages/flet/src/flet/core/notifications.py b/sdk/python/packages/flet/src/flet/core/notifications.py
new file mode 100644
index 0000000000..cb66567012
--- /dev/null
+++ b/sdk/python/packages/flet/src/flet/core/notifications.py
@@ -0,0 +1,450 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any, List, Optional, Union
+
+from flet.core.badge import BadgeValue
+from flet.core.control import Control, OptionalNumber
+from flet.core.exceptions import FletUnsupportedPlatformException
+from flet.core.ref import Ref
+from flet.core.tooltip import TooltipValue
+from flet.core.types import ColorValue, DurationValue, PagePlatform
+
+
+class NotificationActionType(Enum):
+ DEFAULT = "default"
+ DISABLED = "disabled"
+ KEEP_ON_TOP = "keepOnTop"
+ SILENT = "silent"
+ SILENT_BACKGROUND = "silentBackground"
+ DISMISS = "dismiss"
+
+
+class NotificationCategory(Enum):
+ ALARM = "alarm"
+ CALL = "call"
+ EMAIL = "email"
+ ERROR = "error"
+ EVENT = "event"
+ LOCAL_SHARING = "localSharing"
+ MESSAGE = "message"
+ MISSED_CALL = "missedCall"
+ NAVIGATION = "navigation"
+ PROGRESS = "progress"
+ PROMO = "promo"
+ RECOMMENDATION = "recommendation"
+ REMINDER = "reminder"
+ SERVICE = "service"
+ SOCIAL = "social"
+ STATUS = "status"
+ STOPWATCH = "stopwatch"
+ TRANSPORT = "transport"
+ WORKOUT = "workout"
+
+
+class NotificationLayout(Enum):
+ DEFAULT = "default"
+ BIG_PICTURE = "bigPicture"
+ BIG_TEXT = "bigText"
+ INBOX = "inbox"
+ PROGRESS_BAR = "progressBar"
+ MESSAGING = "messaging"
+ MESSAGING_GROUP = "messagingGroup"
+ MEDIA_PLAYER = "mediaPlayer"
+
+
+class NotificationPrivacy(Enum):
+ SECRET = "secret"
+ PRIVATE = "private"
+ PUBLIC = "public"
+
+
+class NotificationGroupSort(Enum):
+ ASCENDING = "ascending"
+ DESCENDING = "descending"
+
+
+class NotificationImportance(Enum):
+ NONE = "none"
+ DEFAULT = "default"
+ MAXIMUM = "maximum"
+ MINIMUM = "minimum"
+ HIGH = "high"
+ LOW = "low"
+
+
+class NotificationRingtoneType(Enum):
+ ALARM = "alarm"
+ NOTIFICATION = "notification"
+ RINGTONE = "ringtone"
+
+
+class NotificationGroupAlertBehavior(Enum):
+ ALL = "all"
+ SUMMARY = "summary"
+ CHILDREN = "children"
+
+
+class NotificationLifeCycle(Enum):
+ FOREGROUND = "foreground"
+ BACKGROUND = "background"
+ TERMINATED = "terminated"
+
+
+@dataclass
+class NotificationInterval:
+ interval: DurationValue
+ time_zone: Optional[str] = None
+ allow_while_idle: bool = False
+ repeats: bool = False
+ precise_alarm: bool = False
+
+
+@dataclass
+class NotificationCalendar:
+ day: Optional[int] = None
+ hour: Optional[int] = None
+ minute: Optional[int] = None
+ second: Optional[int] = None
+ millisecond: Optional[int] = None
+ month: Optional[int] = None
+ weekday: Optional[int] = None
+ week_of_year: Optional[int] = None
+ year: Optional[int] = None
+ era: Optional[int] = None
+ time_zone: Optional[str] = None
+ allow_while_idle: bool = False
+ repeats: bool = False
+ precise_alarm: bool = False
+
+
+@dataclass
+class NotificationContent:
+ id: int
+ channel_key: str
+ title: Optional[str] = None
+ body: Optional[str] = None
+ title_loc_key: Optional[str] = None
+ body_loc_key: Optional[str] = None
+ title_loc_args: Optional[List[str]] = None
+ body_loc_args: Optional[List[str]] = None
+ group_key: Optional[str] = None
+ summary: Optional[str] = None
+ icon: Optional[str] = None
+ large_icon: Optional[str] = None
+ big_picture: Optional[str] = None
+ custom_sound: Optional[str] = None
+ show_when: bool = True
+ wake_up_screen: bool = False
+ full_screen_intent: bool = False
+ critical_alert: bool = False
+ rounded_large_icon: bool = False
+ rounded_big_picture: bool = False
+ auto_dismissible: bool = True
+ color: Optional[ColorValue] = None
+ timeout_after: Optional[DurationValue] = None
+ chronometer: Optional[DurationValue] = None
+ bgcolor: Optional[ColorValue] = None
+ hide_large_icon_on_expand: bool = False
+ locked: Optional[bool] = False
+ progress: OptionalNumber = None
+ badge: Optional[int] = None
+ ticker: Optional[str] = None
+ display_on_foreground: bool = True
+ display_on_background: bool = True
+ duration: Optional[DurationValue] = None
+ playback_speed: OptionalNumber = None
+ action_type: NotificationActionType = NotificationActionType.DEFAULT
+ category: Optional[NotificationCategory] = None
+ layout: Optional[NotificationLayout] = None
+
+
+@dataclass
+class NotificationChannel:
+ channel_key: str
+ channel_name: str
+ channel_description: str
+ channel_group_key: Optional[str] = None
+ channel_show_badge: bool = True
+ critical_alerts: bool = False
+ default_color: Optional[ColorValue] = None
+ enable_lights: bool = True
+ enable_vibration: bool = True
+ led_color: Optional[ColorValue] = None
+ led_on_ms: Optional[int] = None
+ led_off_ms: Optional[int] = None
+ only_alert_once: bool = False
+ play_sound: bool = True
+ sound_source: Optional[str] = None
+ group_key: Optional[str] = None
+ icon: Optional[str] = None
+ locked: bool = False
+ privacy: Optional[NotificationPrivacy] = None
+ group_sort: Optional[NotificationGroupSort] = None
+ importance: Optional[NotificationImportance] = None
+ ringtone_type: Optional[NotificationRingtoneType] = None
+ group_alert_behavior: Optional[NotificationGroupAlertBehavior] = None
+
+
+@dataclass
+class NotificationActionButton:
+ key: str
+ label: str
+ disabled: Optional[bool] = False
+ requires_authentication: Optional[bool] = False
+ dangerous: Optional[bool] = False
+ require_text_input: Optional[bool] = False
+ show_in_compact_view: Optional[bool] = True
+ auto_dismissible: Optional[bool] = True
+ color: Optional[ColorValue] = None
+ icon: Optional[str] = None
+ action_type: Optional[NotificationActionType] = None
+
+
+class Notifications(Control):
+ def __init__(
+ self,
+ channels: List[NotificationChannel],
+ language_code: Optional[str] = None,
+ #
+ # Control
+ #
+ ref: Optional[Ref] = None,
+ opacity: OptionalNumber = None,
+ tooltip: Optional[TooltipValue] = None,
+ badge: Optional[BadgeValue] = None,
+ visible: Optional[bool] = None,
+ data: Any = None,
+ ):
+ Control.__init__(
+ self,
+ ref=ref,
+ opacity=opacity,
+ tooltip=tooltip,
+ badge=badge,
+ visible=visible,
+ data=data,
+ )
+
+ self.__channels = channels
+ self.language_code = language_code
+
+ def _get_control_name(self):
+ return "notifications"
+
+ def before_update(self):
+ super().before_update()
+ assert self.page is not None, "Notifications must be added to page first."
+ if self.page.web or self.page.platform not in [
+ PagePlatform.ANDROID,
+ PagePlatform.IOS,
+ ]:
+ raise FletUnsupportedPlatformException(
+ "This control is supported on Android and iOS platforms only."
+ )
+ self._set_attr_json("channels", self.__channels)
+
+ def show(
+ self,
+ content: NotificationContent,
+ action_buttons: Optional[List[NotificationActionButton]] = None,
+ schedule: Optional[Union[NotificationCalendar, NotificationInterval]] = None,
+ ) -> None:
+ self.invoke_method(
+ "show",
+ arguments={
+ "content": self._convert_attr_json(content),
+ "action_buttons": self._convert_attr_json(action_buttons),
+ "schedule": self._convert_attr_json(schedule),
+ "schedule_parser": "interval"
+ if isinstance(schedule, NotificationInterval)
+ else "calendar",
+ },
+ )
+
+ def dismiss(
+ self,
+ id: Optional[int] = None,
+ channel_key: Optional[str] = None,
+ group_key: Optional[str] = None,
+ ) -> None:
+ self.invoke_method(
+ "dismiss",
+ arguments={
+ "id": id,
+ "channel_key": channel_key,
+ "group_key": group_key,
+ },
+ )
+
+ def dismiss_all(self) -> None:
+ self.invoke_method("dismiss_all")
+
+ def cancel(
+ self,
+ id: Optional[int] = None,
+ channel_key: Optional[str] = None,
+ group_key: Optional[str] = None,
+ ) -> None:
+ self.invoke_method(
+ "cancel",
+ arguments={
+ "id": id,
+ "channel_key": channel_key,
+ "group_key": group_key,
+ },
+ )
+
+ def cancel_schedule(
+ self,
+ id: Optional[int] = None,
+ channel_key: Optional[str] = None,
+ group_key: Optional[str] = None,
+ ) -> None:
+ self.invoke_method(
+ "cancel_schedule",
+ arguments={
+ "id": id,
+ "channel_key": channel_key,
+ "group_key": group_key,
+ },
+ )
+
+ def cancel_all_schedules(self) -> None:
+ self.invoke_method("cancel_all_schedules")
+
+ # badge_counter
+ def get_badge_counter(self, wait_timeout: float = 10) -> int:
+ return self.invoke_method(
+ "get_badge_counter",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="int",
+ )
+
+ def set_badge_counter(self, value: int) -> None:
+ self.invoke_method("set_badge_counter", arguments={"value": str(value)})
+
+ def increment_badge_counter(self, wait_timeout: float = 10) -> bool:
+ return self.invoke_method(
+ "increment_badge_counter",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="bool",
+ )
+
+ def decrement_badge_counter(self, wait_timeout: float = 10) -> bool:
+ return self.invoke_method(
+ "decrement_badge_counter",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="bool",
+ )
+
+ def reset_badge_counter(self) -> None:
+ self.invoke_method("reset_badge_counter")
+
+ # channels
+ def set_channel(
+ self, channel: NotificationChannel, force_update: bool = False
+ ) -> None:
+ self.invoke_method(
+ "set_channel",
+ arguments={
+ "channel": self._convert_attr_json(channel),
+ "force_update": force_update,
+ },
+ )
+
+ def remove_channel(self, channel_key: str) -> None:
+ self.invoke_method("remove_channel", arguments={"channel_key": channel_key})
+
+ # permissions
+ def is_allowed(self, wait_timeout: float = 10) -> bool:
+ return self.invoke_method(
+ "is_allowed",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="bool",
+ )
+
+ def request_permission(self, wait_timeout: float = 10) -> bool:
+ return self.invoke_method(
+ "request_permission",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="bool",
+ )
+
+ # time
+ def get_local_timezone_identifier(self, wait_timeout: float = 10) -> str:
+ return self.invoke_method(
+ "get_local_timezone_identifier",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ )
+
+ def get_utc_timezone_identifier(self, wait_timeout: float = 10) -> str:
+ return self.invoke_method(
+ "get_utc_timezone_identifier",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ )
+
+ # others
+ def show_alarm_page(self) -> None:
+ self.invoke_method("show_alarm_page")
+
+ def get_initial_action(self, remove_from_action_events: bool = False) -> str:
+ return self.invoke_method(
+ "get_initial_action",
+ arguments={"remove_from_action_events": str(remove_from_action_events)},
+ )
+
+ def show_global_dnd_override_page(self) -> None:
+ self.invoke_method("show_global_dnd_override_page")
+
+ def get_lifecycle(self, wait_timeout: float = 10) -> NotificationLifeCycle:
+ result = self.invoke_method(
+ "get_app_lifecycle",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ )
+ return NotificationLifeCycle(result)
+
+ def get_localization(self, wait_timeout: float = 10) -> str:
+ return self.invoke_method(
+ "get_localization",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ )
+
+ def is_active_on_status_bar(self, id: int, wait_timeout: float = 10) -> bool:
+ return self.invoke_method(
+ "is_active_on_status_bar",
+ arguments={"id": str(id)},
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="bool",
+ )
+
+ def get_ids_active_on_status_bar(self, wait_timeout: float = 10) -> List[int]:
+ return self.invoke_method(
+ "get_ids_active_on_status_bar",
+ wait_for_result=True,
+ wait_timeout=wait_timeout,
+ result_type="json_encoded",
+ )
+
+ # channels
+ @property
+ def channels(self) -> List[NotificationChannel]:
+ return self.__channels
+
+ # language_code
+ @property
+ def language_code(self) -> Optional[str]:
+ return self._get_attr("languageCode")
+
+ @language_code.setter
+ def language_code(self, value: Optional[str]):
+ self._set_attr("languageCode", value)
diff --git a/sdk/python/packages/flet/src/flet/core/page.py b/sdk/python/packages/flet/src/flet/core/page.py
index b2b3eb8eda..ea36116329 100644
--- a/sdk/python/packages/flet/src/flet/core/page.py
+++ b/sdk/python/packages/flet/src/flet/core/page.py
@@ -25,6 +25,7 @@
TypeVar,
Union,
cast,
+ Literal,
)
from urllib.parse import urlparse
@@ -1187,7 +1188,10 @@ def _invoke_method(
control_id: Optional[str] = "",
wait_for_result: Optional[bool] = False,
wait_timeout: Optional[float] = 5,
- ) -> Optional[str]:
+ result_type: Literal[
+ "string", "int", "float", "bool", "json_encoded"
+ ] = "string",
+ ) -> Optional[Any]:
method_id = uuid.uuid4().hex
# register callback
@@ -1218,8 +1222,22 @@ def _invoke_method(
result, err = self.__method_call_results.pop(evt)
if err:
raise Exception(err)
+
if result is None or result == "null":
return None
+
+ # parse result according to result_type
+ try:
+ if result_type == "int":
+ return int(result)
+ elif result_type == "float":
+ return float(result)
+ elif result_type == "bool":
+ return result == "true"
+ elif result_type == "json_encoded":
+ return json.loads(result)
+ except Exception:
+ return result
return result
async def _invoke_method_async(
@@ -1229,7 +1247,10 @@ async def _invoke_method_async(
control_id: Optional[str] = "",
wait_for_result: Optional[bool] = False,
wait_timeout: Optional[float] = 5,
- ) -> Optional[str]:
+ result_type: Literal[
+ "string", "int", "float", "bool", "json_encoded"
+ ] = "string",
+ ) -> Optional[Any]:
method_id = uuid.uuid4().hex
# register callback
@@ -1264,6 +1285,19 @@ async def _invoke_method_async(
raise Exception(err)
if result == "null":
return None
+
+ # parse result according to result_type
+ try:
+ if result_type == "int":
+ return int(result)
+ elif result_type == "float":
+ return float(result)
+ elif result_type == "bool":
+ return result == "true"
+ elif result_type == "json_encoded":
+ return json.loads(result)
+ except Exception:
+ return result
return result
def __on_invoke_method_result(self, e) -> None: