diff --git a/.github/scripts/clean-pypi.sh b/.github/scripts/clean-pypi.sh index 3a8043a44e..8b176058af 100644 --- a/.github/scripts/clean-pypi.sh +++ b/.github/scripts/clean-pypi.sh @@ -1,9 +1,24 @@ # set PYPI_CLEANUP_PASSWORD with pypi.org password -VER="0\.70\.0\.dev(?!6176)" +VER="0\.70\.0\.dev(?!6907)" uv tool install pypi-cleanup uvx pypi-cleanup -u flet -p flet -y -r $VER --do-it uvx pypi-cleanup -u flet -p flet-cli -y -r $VER --do-it uvx pypi-cleanup -u flet -p flet-desktop -y -r $VER --do-it uvx pypi-cleanup -u flet -p flet-desktop-light -y -r $VER --do-it uvx pypi-cleanup -u flet -p flet-web -y -r $VER --do-it + +# modules +uvx pypi-cleanup -u flet -p flet-ads -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-audio -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-audio-recorder -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-charts -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-datatable2 -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-flashlight -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-geolocator -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-lottie -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-map -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-permission-handler -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-rive -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-video -y -r $VER --do-it +uvx pypi-cleanup -u flet -p flet-webview -y -r $VER --do-it diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 18f1748490..01c0d4e94d 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,8 @@ import FlutterMacOS import Foundation import audioplayers_darwin +import battery_plus +import connectivity_plus import device_info_plus import file_picker import geolocator_apple @@ -15,7 +17,9 @@ import package_info_plus import path_provider_foundation import record_macos import rive_common +import screen_brightness_macos import screen_retriever_macos +import share_plus import shared_preferences_foundation import url_launcher_macos import volume_controller @@ -26,6 +30,8 @@ import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) @@ -35,7 +41,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) + ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) diff --git a/client/pubspec.lock b/client/pubspec.lock index bc1b237618..e768a501b1 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -81,6 +81,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.1" + battery_plus: + dependency: transitive + description: + name: battery_plus + sha256: ad16fcb55b7384be6b4bbc763d5e2031ac7ea62b2d9b6b661490c7b9741155bf + url: "https://pub.dev" + source: hosted + version: "7.0.0" + battery_plus_platform_interface: + dependency: transitive + description: + name: battery_plus_platform_interface + sha256: e8342c0f32de4b1dfd0223114b6785e48e579bfc398da9471c9179b907fa4910 + url: "https://pub.dev" + source: hosted + version: "2.0.1" boolean_selector: dependency: transitive description: @@ -129,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" cross_file: dependency: transitive description: @@ -766,6 +798,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" msgpack_dart: dependency: transitive description: @@ -782,14 +822,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - package_info_plus: + nm: dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + package_info_plus: + dependency: "direct main" description: name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "9.0.0" package_info_plus_platform_interface: dependency: transitive description: @@ -1054,6 +1102,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + screen_brightness: + dependency: transitive + description: + name: screen_brightness + sha256: "5f70754028f169f059fdc61112a19dcbee152f8b293c42c848317854d650cba3" + url: "https://pub.dev" + source: hosted + version: "2.1.7" screen_brightness_android: dependency: transitive description: @@ -1062,6 +1118,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + screen_brightness_ios: + dependency: transitive + description: + name: screen_brightness_ios + sha256: "2493953340ecfe8f4f13f61db50ce72533a55b0bbd58ba1402893feecf3727f5" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + screen_brightness_macos: + dependency: transitive + description: + name: screen_brightness_macos + sha256: "4edf330ad21078686d8bfaf89413325fbaf571dcebe1e89254d675a3f288b5b9" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + screen_brightness_ohos: + dependency: transitive + description: + name: screen_brightness_ohos + sha256: a93a263dcd39b5c56e589eb495bcd001ce65cdd96ff12ab1350683559d5c5bb7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" screen_brightness_platform_interface: dependency: transitive description: @@ -1070,6 +1150,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + screen_brightness_windows: + dependency: transitive + description: + name: screen_brightness_windows + sha256: d3518bf0f5d7a884cee2c14449ae0b36803802866de09f7ef74077874b6b2448 + url: "https://pub.dev" + source: hosted + version: "2.1.0" screen_retriever: dependency: transitive description: @@ -1122,10 +1210,10 @@ packages: dependency: transitive description: name: sensors_plus - sha256: "89e2bfc3d883743539ce5774a2b93df61effde40ff958ecad78cd66b1a8b8d52" + sha256: "56e8cd4260d9ed8e00ecd8da5d9fdc8a1b2ec12345a750dfa51ff83fcf12e3fa" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "7.0.0" sensors_plus_platform_interface: dependency: transitive description: @@ -1134,6 +1222,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + url: "https://pub.dev" + source: hosted + version: "12.0.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" shared_preferences: dependency: transitive description: @@ -1299,6 +1403,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + upower: + dependency: transitive + description: + name: upower + sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf + url: "https://pub.dev" + source: hosted + version: "0.7.0" uri_parser: dependency: transitive description: @@ -1428,13 +1540,13 @@ packages: source: hosted version: "3.4.0" wakelock_plus: - dependency: transitive + dependency: "direct main" description: name: wakelock_plus - sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.4.0" wakelock_plus_platform_interface: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index ec66619c2c..bb7a554430 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -74,6 +74,8 @@ dependencies: path: ../sdk/python/packages/flet-charts/src/flutter/flet_charts cupertino_icons: ^1.0.6 + wakelock_plus: ^1.4.0 + package_info_plus: ^9.0.0 integration_test: sdk: flutter @@ -81,7 +83,7 @@ dependencies: dependency_overrides: flet: path: ../packages/flet - # webview_flutter_android: ^4.0.0 + package_info_plus: ^9.0.0 dev_dependencies: flutter_test: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 754c15c2ff..1fc0608790 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -7,13 +7,17 @@ #include "generated_plugin_registrant.h" #include +#include +#include #include #include #include #include #include #include +#include #include +#include #include #include #include @@ -22,6 +26,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + BatteryPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( @@ -34,8 +42,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); RivePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("RivePlugin")); + ScreenBrightnessWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VolumeControllerPluginCApiRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 72a5864a6b..ce206ef9ef 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -4,13 +4,17 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows + battery_plus + connectivity_plus geolocator_windows media_kit_libs_windows_video media_kit_video permission_handler_windows record_windows rive_common + screen_brightness_windows screen_retriever_windows + share_plus url_launcher_windows volume_controller window_manager diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index 3bac74ab36..a74c1dd2fe 100644 --- a/packages/flet/lib/src/controls/page.dart +++ b/packages/flet/lib/src/controls/page.dart @@ -56,9 +56,8 @@ class _PageControlState extends State with WidgetsBindingObserver { late final SimpleRouterDelegate _routerDelegate; late final RouteParser _routeParser; late final AppLifecycleListener _appLifecycleListener; - ServiceRegistry? _pageServices; - ServiceRegistry? _userServices; - String? _userServicesUid; + ServiceRegistry? _services; + String? _servicesUid; ServiceBinding? _windowService; Control? _windowControl; bool? _prevOnKeyboardEvent; @@ -67,7 +66,6 @@ class _PageControlState extends State with WidgetsBindingObserver { final Map _multiViews = {}; bool _registeredFromMultiViews = false; - List? _appliedDeviceOrientations; @override void initState() { @@ -137,8 +135,7 @@ class _PageControlState extends State with WidgetsBindingObserver { } widget.control.removeInvokeMethodListener(_invokeMethod); widget.control.removeListener(_onPageControlChanged); - _pageServices?.dispose(); - _userServices?.dispose(); + _services?.dispose(); _windowService?.dispose(); super.dispose(); } @@ -153,26 +150,21 @@ class _PageControlState extends State with WidgetsBindingObserver { } var backend = FletBackend.of(context); - _pageServices ??= ServiceRegistry( - control: widget.control, - propertyName: "_services", - backend: backend); - - var userServicesControl = widget.control.child("_user_services"); - if (userServicesControl != null) { - var uid = userServicesControl.internals?["uid"]; - if (_userServices == null || _userServicesUid != uid) { - _userServices?.dispose(); - _userServices = ServiceRegistry( - control: userServicesControl, + var servicesControl = widget.control.child("_services"); + if (servicesControl != null) { + var uid = servicesControl.internals?["uid"]; + if (_services == null || _servicesUid != uid) { + _services?.dispose(); + _services = ServiceRegistry( + control: servicesControl, propertyName: "_services", backend: backend); - _userServicesUid = uid; + _servicesUid = uid; } - } else if (_userServices != null) { - _userServices?.dispose(); - _userServices = null; - _userServicesUid = null; + } else if (_services != null) { + _services?.dispose(); + _services = null; + _servicesUid = null; } var windowControl = widget.control.child("window", visibleOnly: false); diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index 2f5f97013e..eb633ca196 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -107,16 +107,26 @@ import 'flet_extension.dart'; import 'flet_service.dart'; import 'models/control.dart'; import 'services/browser_context_menu.dart'; +import 'services/battery.dart'; +import 'services/accelerometer.dart'; import 'services/clipboard.dart'; +import 'services/connectivity.dart'; import 'services/file_picker.dart'; +import 'services/barometer.dart'; import 'services/haptic_feedback.dart'; +import 'services/gyroscope.dart'; +import 'services/magnetometer.dart'; +import 'services/share.dart'; import 'services/semantics_service.dart'; import 'services/shake_detector.dart'; import 'services/shared_preferences.dart'; +import 'services/screen_brightness.dart'; import 'services/storage_paths.dart'; import 'services/tester.dart'; import 'services/url_launcher.dart'; +import 'services/wakelock.dart'; import 'services/window.dart'; +import 'services/user_accelerometer.dart'; import 'utils/cupertino_icons.dart'; import 'utils/material_icons.dart'; @@ -370,26 +380,46 @@ class FletCoreExtension extends FletExtension { switch (control.type) { case "BrowserContextMenu": return BrowserContextMenuService(control: control); + case "Accelerometer": + return AccelerometerService(control: control); + case "Barometer": + return BarometerService(control: control); + case "Battery": + return BatteryService(control: control); case "Clipboard": return ClipboardService(control: control); + case "Connectivity": + return ConnectivityService(control: control); + case "Share": + return ShareService(control: control); case "FilePicker": return FilePickerService(control: control); case "HapticFeedback": return HapticFeedbackService(control: control); + case "Gyroscope": + return GyroscopeService(control: control); case "ShakeDetector": return ShakeDetectorService(control: control); case "SharedPreferences": return SharedPreferencesService(control: control); case "SemanticsService": return SemanticsServiceControl(control: control); + case "Magnetometer": + return MagnetometerService(control: control); + case "ScreenBrightness": + return ScreenBrightnessService(control: control); case "StoragePaths": return StoragePaths(control: control); case "Window": return WindowService(control: control); case "Tester": return TesterService(control: control); + case "UserAccelerometer": + return UserAccelerometerService(control: control); case "UrlLauncher": return UrlLauncherService(control: control); + case "Wakelock": + return WakelockService(control: control); default: return null; } diff --git a/packages/flet/lib/src/services/accelerometer.dart b/packages/flet/lib/src/services/accelerometer.dart new file mode 100644 index 0000000000..3c35ac309b --- /dev/null +++ b/packages/flet/lib/src/services/accelerometer.dart @@ -0,0 +1,22 @@ +import 'package:sensors_plus/sensors_plus.dart'; + +import 'base_sensor.dart'; + +class AccelerometerService extends BaseSensorService { + AccelerometerService({required super.control}); + + @override + Stream sensorStream(Duration samplingPeriod) { + return accelerometerEventStream(samplingPeriod: samplingPeriod); + } + + @override + Map serializeEvent(AccelerometerEvent event) { + return { + "x": event.x, + "y": event.y, + "z": event.z, + "timestamp": event.timestamp, + }; + } +} diff --git a/packages/flet/lib/src/services/barometer.dart b/packages/flet/lib/src/services/barometer.dart new file mode 100644 index 0000000000..af99a0c40c --- /dev/null +++ b/packages/flet/lib/src/services/barometer.dart @@ -0,0 +1,20 @@ +import 'package:sensors_plus/sensors_plus.dart'; + +import 'base_sensor.dart'; + +class BarometerService extends BaseSensorService { + BarometerService({required super.control}); + + @override + Stream sensorStream(Duration samplingPeriod) { + return barometerEventStream(samplingPeriod: samplingPeriod); + } + + @override + Map serializeEvent(BarometerEvent event) { + return { + "pressure": event.pressure, + "timestamp": event.timestamp, + }; + } +} diff --git a/packages/flet/lib/src/services/base_sensor.dart b/packages/flet/lib/src/services/base_sensor.dart new file mode 100644 index 0000000000..f7b4831c4e --- /dev/null +++ b/packages/flet/lib/src/services/base_sensor.dart @@ -0,0 +1,104 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:sensors_plus/sensors_plus.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; + +abstract class BaseSensorService extends FletService { + BaseSensorService({required super.control}); + + StreamSubscription? _subscription; + bool _enabled = true; + bool _hasReadingSubscribers = false; + bool _hasErrorSubscribers = false; + Duration _interval = SensorInterval.normalInterval; + bool _cancelOnError = true; + + Duration get defaultInterval => SensorInterval.normalInterval; + + String get eventName => "reading"; + + Stream sensorStream(Duration samplingPeriod); + + Map serializeEvent(T event); + + @override + void init() { + super.init(); + _updateConfig(forceRestart: true); + } + + @override + void update() { + _updateConfig(); + } + + void _updateConfig({bool forceRestart = false}) { + var enabled = control.getBool("enabled", true) ?? true; + var interval = + control.getDuration("interval", defaultInterval) ?? defaultInterval; + if (interval.isNegative) { + interval = defaultInterval; + } + var hasReadingSubscribers = control.getBool("on_$eventName") == true; + var hasErrorSubscribers = control.getBool("on_error") == true; + var cancelOnError = control.getBool("cancel_on_error", true) ?? true; + + if (forceRestart || + enabled != _enabled || + interval != _interval || + hasReadingSubscribers != _hasReadingSubscribers || + hasErrorSubscribers != _hasErrorSubscribers || + cancelOnError != _cancelOnError) { + _enabled = enabled; + _interval = interval; + _hasReadingSubscribers = hasReadingSubscribers; + _hasErrorSubscribers = hasErrorSubscribers; + _cancelOnError = cancelOnError; + _restart(); + } + } + + void _restart() { + _subscription?.cancel(); + _subscription = null; + + if (!_enabled || (!_hasReadingSubscribers && !_hasErrorSubscribers)) { + return; + } + + final samplingPeriod = _interval; + try { + _subscription = sensorStream(samplingPeriod).listen( + (event) { + if (_hasReadingSubscribers) { + final payload = serializeEvent(event); + control.triggerEvent(eventName, payload); + } + }, + onError: (error, stackTrace) { + if (_hasErrorSubscribers) { + control.triggerEvent("error", + {"message": error?.toString() ?? "Unknown sensor error"}); + } else { + debugPrint( + "Error listening to ${control.type} sensor stream: $error"); + } + }, + cancelOnError: _cancelOnError, + ); + } catch (error) { + debugPrint("Failed to initialize ${control.type} sensor stream: $error"); + } + } + + @override + void dispose() { + _subscription?.cancel(); + _subscription = null; + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/battery.dart b/packages/flet/lib/src/services/battery.dart new file mode 100644 index 0000000000..dbd421734c --- /dev/null +++ b/packages/flet/lib/src/services/battery.dart @@ -0,0 +1,64 @@ +import 'dart:async'; + +import 'package:battery_plus/battery_plus.dart'; +import 'package:flutter/foundation.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; + +class BatteryService extends FletService { + final Battery _battery = Battery(); + StreamSubscription? _stateSub; + + BatteryService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("BatteryService(${control.id}).init"); + control.addInvokeMethodListener(_invokeMethod); + _updateListeners(); + } + + @override + void update() { + _updateListeners(); + } + + Future _invokeMethod(String name, dynamic args) async { + switch (name) { + case "get_battery_level": + return _battery.batteryLevel; + case "get_battery_state": + final state = await _battery.batteryState; + return state.name; + case "is_in_battery_save_mode": + return _battery.isInBatterySaveMode; + default: + throw Exception("Unknown Battery method: $name"); + } + } + + void _updateListeners() { + final listenState = control.getBool("on_state_change") == true; + if (listenState && _stateSub == null) { + _stateSub = _battery.onBatteryStateChanged.listen((state) { + control.triggerEvent("state_change", {"state": state.name}); + }, onError: (error) { + debugPrint("BatteryService: error listening to state changes: $error"); + }); + } else if (!listenState && _stateSub != null) { + _stateSub?.cancel(); + _stateSub = null; + } + } + + @override + void dispose() { + debugPrint("BatteryService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + _stateSub?.cancel(); + _stateSub = null; + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/connectivity.dart b/packages/flet/lib/src/services/connectivity.dart new file mode 100644 index 0000000000..1fabe03b96 --- /dev/null +++ b/packages/flet/lib/src/services/connectivity.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/foundation.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; + +class ConnectivityService extends FletService { + final Connectivity _connectivity = Connectivity(); + StreamSubscription>? _subscription; + + ConnectivityService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("ConnectivityService(${control.id}).init"); + control.addInvokeMethodListener(_invokeMethod); + _updateListeners(); + } + + @override + void update() { + _updateListeners(); + } + + Future _invokeMethod(String name, dynamic args) async { + switch (name) { + case "get_connectivity": + final results = await _connectivity.checkConnectivity(); + return results.map((r) => r.name).toList(); + default: + throw Exception("Unknown Connectivity method: $name"); + } + } + + void _updateListeners() { + final listenChange = control.getBool("on_change") == true; + if (listenChange && _subscription == null) { + _subscription = _connectivity.onConnectivityChanged.listen( + (List result) { + control.triggerEvent( + "change", {"connectivity": result.map((r) => r.name).toList()}); + }, onError: (error) { + debugPrint( + "ConnectivityService: error listening to connectivity: $error"); + }); + } else if (!listenChange && _subscription != null) { + _subscription?.cancel(); + _subscription = null; + } + } + + @override + void dispose() { + debugPrint("ConnectivityService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + _subscription?.cancel(); + _subscription = null; + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/gyroscope.dart b/packages/flet/lib/src/services/gyroscope.dart new file mode 100644 index 0000000000..5cb95f4670 --- /dev/null +++ b/packages/flet/lib/src/services/gyroscope.dart @@ -0,0 +1,22 @@ +import 'package:sensors_plus/sensors_plus.dart'; + +import 'base_sensor.dart'; + +class GyroscopeService extends BaseSensorService { + GyroscopeService({required super.control}); + + @override + Stream sensorStream(Duration samplingPeriod) { + return gyroscopeEventStream(samplingPeriod: samplingPeriod); + } + + @override + Map serializeEvent(GyroscopeEvent event) { + return { + "x": event.x, + "y": event.y, + "z": event.z, + "timestamp": event.timestamp, + }; + } +} diff --git a/packages/flet/lib/src/services/magnetometer.dart b/packages/flet/lib/src/services/magnetometer.dart new file mode 100644 index 0000000000..6d6124bde6 --- /dev/null +++ b/packages/flet/lib/src/services/magnetometer.dart @@ -0,0 +1,22 @@ +import 'package:sensors_plus/sensors_plus.dart'; + +import 'base_sensor.dart'; + +class MagnetometerService extends BaseSensorService { + MagnetometerService({required super.control}); + + @override + Stream sensorStream(Duration samplingPeriod) { + return magnetometerEventStream(samplingPeriod: samplingPeriod); + } + + @override + Map serializeEvent(MagnetometerEvent event) { + return { + "x": event.x, + "y": event.y, + "z": event.z, + "timestamp": event.timestamp, + }; + } +} diff --git a/packages/flet/lib/src/services/screen_brightness.dart b/packages/flet/lib/src/services/screen_brightness.dart new file mode 100644 index 0000000000..9b1b3ce290 --- /dev/null +++ b/packages/flet/lib/src/services/screen_brightness.dart @@ -0,0 +1,134 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:screen_brightness/screen_brightness.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; +import '../utils/platform.dart'; + +class ScreenBrightnessService extends FletService { + final ScreenBrightness _screenBrightness = ScreenBrightness(); + StreamSubscription? _systemSubscription; + StreamSubscription? _applicationSubscription; + + ScreenBrightnessService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("ScreenBrightnessService(${control.id}).init"); + control.addInvokeMethodListener(_invokeMethod); + _updateListeners(); + } + + @override + void update() { + _updateListeners(); + } + + Future _invokeMethod(String name, dynamic args) async { + if (isWebPlatform()) { + return null; + } + + switch (name) { + case "get_system_screen_brightness": + return _screenBrightness.system; + case "can_change_system_screen_brightness": + return _screenBrightness.canChangeSystemBrightness; + case "set_system_screen_brightness": + final brightness = parseDouble(args["value"]); + if (brightness == null) { + throw ArgumentError.notNull("value"); + } + return _screenBrightness.setSystemScreenBrightness(brightness); + case "get_application_screen_brightness": + return _screenBrightness.application; + case "set_application_screen_brightness": + final brightness = parseDouble(args["value"]); + if (brightness == null) { + throw ArgumentError.notNull("value"); + } + return _screenBrightness.setApplicationScreenBrightness(brightness); + case "reset_application_screen_brightness": + return _screenBrightness.resetApplicationScreenBrightness(); + case "is_animate": + return _screenBrightness.isAnimate; + case "set_animate": + final isAnimate = parseBool(args["value"]); + if (isAnimate == null) { + throw ArgumentError.notNull("value"); + } + return _screenBrightness.setAnimate(isAnimate); + case "is_auto_reset": + return _screenBrightness.isAutoReset; + case "set_auto_reset": + final isAutoReset = parseBool(args["value"]); + if (isAutoReset == null) { + throw ArgumentError.notNull("value"); + } + return _screenBrightness.setAutoReset(isAutoReset); + default: + throw Exception("Unknown ScreenBrightness method: $name"); + } + } + + void _updateListeners() { + if (isWebPlatform()) { + _disposeSubscriptions(); + return; + } + + final listenSystem = + control.getBool("on_system_screen_brightness_change") == true; + final listenApplication = + control.getBool("on_application_screen_brightness_change") == true; + + if (listenSystem && _systemSubscription == null) { + _systemSubscription = _screenBrightness.onSystemScreenBrightnessChanged + .listen((value) { + control.triggerEvent( + "system_screen_brightness_change", {"brightness": value}); + }, onError: (error) { + debugPrint( + "ScreenBrightnessService: error listening for system changes: $error"); + }); + } else if (!listenSystem && _systemSubscription != null) { + _systemSubscription?.cancel(); + _systemSubscription = null; + } + + if (listenApplication && _applicationSubscription == null) { + _applicationSubscription = + _screenBrightness.onApplicationScreenBrightnessChanged.listen( + (value) { + control.triggerEvent( + "application_screen_brightness_change", {"brightness": value}); + }, + onError: (error) { + debugPrint( + "ScreenBrightnessService: error listening for application changes: $error"); + }, + ); + } else if (!listenApplication && _applicationSubscription != null) { + _applicationSubscription?.cancel(); + _applicationSubscription = null; + } + } + + void _disposeSubscriptions() { + _systemSubscription?.cancel(); + _systemSubscription = null; + _applicationSubscription?.cancel(); + _applicationSubscription = null; + } + + @override + void dispose() { + debugPrint("ScreenBrightnessService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + _disposeSubscriptions(); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/share.dart b/packages/flet/lib/src/services/share.dart new file mode 100644 index 0000000000..6ca6ca87f5 --- /dev/null +++ b/packages/flet/lib/src/services/share.dart @@ -0,0 +1,189 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as p; +import 'package:share_plus/share_plus.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; + +class ShareService extends FletService { + ShareService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("ShareService(${control.id}).init"); + control.addInvokeMethodListener(_invokeMethod); + } + + Future _invokeMethod(String name, dynamic args) async { + switch (name) { + case "share_text": + return _share( + text: args["text"], + title: args["title"], + subject: args["subject"], + previewThumbnail: _parseShareFile(args["preview_thumbnail"]), + sharePositionOrigin: _parseRect(args["share_position_origin"]), + downloadFallbackEnabled: + parseBool(args["download_fallback_enabled"], true) ?? true, + mailToFallbackEnabled: + parseBool(args["mail_to_fallback_enabled"], true) ?? true, + excludedCupertinoActivities: _parseCupertinoActivityTypes( + args["excluded_cupertino_activities"]), + ); + case "share_uri": + return _share( + uri: args["uri"], + sharePositionOrigin: _parseRect(args["share_position_origin"]), + excludedCupertinoActivities: _parseCupertinoActivityTypes( + args["excluded_cupertino_activities"]), + ); + case "share_files": + return _share( + files: _parseShareFiles(args["files"]), + title: args["title"], + text: args["text"], + subject: args["subject"], + previewThumbnail: _parseShareFile(args["preview_thumbnail"]), + sharePositionOrigin: _parseRect(args["share_position_origin"]), + downloadFallbackEnabled: + parseBool(args["download_fallback_enabled"], true) ?? true, + mailToFallbackEnabled: + parseBool(args["mail_to_fallback_enabled"], true) ?? true, + excludedCupertinoActivities: _parseCupertinoActivityTypes( + args["excluded_cupertino_activities"]), + fileNameOverrides: _parseFileNameOverrides(args["files"]), + ); + default: + throw Exception("Unknown Share method: $name"); + } + } + + Future> _share({ + String? text, + String? title, + String? subject, + String? uri, + List? files, + XFile? previewThumbnail, + Rect? sharePositionOrigin, + bool downloadFallbackEnabled = true, + bool mailToFallbackEnabled = true, + List? excludedCupertinoActivities, + List? fileNameOverrides, + }) async { + final params = ShareParams( + text: text, + title: title, + subject: subject, + uri: uri != null ? Uri.parse(uri) : null, + files: files, + previewThumbnail: previewThumbnail, + sharePositionOrigin: sharePositionOrigin, + downloadFallbackEnabled: downloadFallbackEnabled, + mailToFallbackEnabled: mailToFallbackEnabled, + excludedCupertinoActivities: excludedCupertinoActivities, + fileNameOverrides: fileNameOverrides); + + final result = await SharePlus.instance.share(params); + return {"status": result.status.name, "raw": result.raw}; + } + + List? _parseShareFiles(dynamic value) { + if (value is List) { + return value.map((e) => _parseShareFile(e)!).toList(); + } + return null; + } + + List? _parseFileNameOverrides(dynamic value) { + if (value is List) { + final names = value.map((e) => _extractFileName(e)).toList(); + return names.isEmpty ? null : names; + } + return null; + } + + String _extractFileName(dynamic value) { + if (value is Map) { + final name = (value["name"] as String?)?.trim(); + if (name != null && name.isNotEmpty) { + return name; + } + final path = (value["path"] as String?)?.trim(); + if (path != null && path.isNotEmpty) { + final base = p.basename(path); + if (base.isNotEmpty) { + return base; + } + } + } + return "shared_file"; + } + + XFile? _parseShareFile(dynamic value) { + if (value is Map) { + final path = value["path"] as String?; + final data = value["data"]; + final mimeType = value["mime_type"] as String?; + final name = value["name"] as String?; + + if (path != null) { + final resolvedName = (name ?? p.basename(path)).trim().isNotEmpty + ? (name ?? p.basename(path)).trim() + : "shared_file"; + return XFile(path, name: resolvedName); + } + + if (data != null) { + Uint8List bytes; + if (data is Uint8List) { + bytes = data; + } else if (data is List) { + bytes = Uint8List.fromList(data); + } else { + throw ArgumentError("data must be Uint8List or List"); + } + final resolvedName = + name != null && name.isNotEmpty ? name : "shared_file"; + return XFile.fromData(bytes, mimeType: mimeType, name: resolvedName); + } + } + return null; + } + + Rect? _parseRect(dynamic value) { + if (value is Map) { + final x = parseDouble(value["x"], 0) ?? 0; + final y = parseDouble(value["y"], 0) ?? 0; + final width = parseDouble(value["width"], 0) ?? 0; + final height = parseDouble(value["height"], 0) ?? 0; + return Rect.fromLTWH(x, y, width, height); + } + return null; + } + + List? _parseCupertinoActivityTypes(dynamic value) { + if (value is List) { + final types = []; + for (final v in value) { + try { + final match = CupertinoActivityType.values + .firstWhere((e) => e.name == v || e.value == v); + types.add(match); + } catch (_) {} + } + return types.isEmpty ? null : types; + } + return null; + } + + @override + void dispose() { + debugPrint("ShareService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/url_launcher.dart b/packages/flet/lib/src/services/url_launcher.dart index 8ec619bde9..113a9ae98e 100644 --- a/packages/flet/lib/src/services/url_launcher.dart +++ b/packages/flet/lib/src/services/url_launcher.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -26,14 +27,25 @@ class UrlLauncherService extends FletService { switch (name) { case "launch_url": return openWebBrowser(parseUrl(args["url"]!)!, - webPopupWindow: parseBool(args["web_popup_window"], false)!, - webPopupWindowName: args["web_popup_window_name"], - webPopupWindowWidth: parseInt(args["web_popup_window_width"]), - webPopupWindowHeight: parseInt(args["web_popup_window_height"])); + mode: _parseLaunchMode(args["mode"]), + webViewConfiguration: + _parseWebViewConfiguration(args["web_view_configuration"]), + browserConfiguration: + _parseBrowserConfiguration(args["browser_configuration"]), + webOnlyWindowName: args["web_only_window_name"]); case "can_launch_url": return canLaunchUrl(Uri.parse(parseUrl(args["url"]!)!.url)); case "close_in_app_web_view": return closeInAppWebView(); + case "open_window": + return openWindow(parseUrl(args["url"]!)!, + title: args["title"], + width: parseDouble(args["width"]), + height: parseDouble(args["height"])); + case "supports_launch_mode": + return supportsLaunchMode(_parseLaunchMode(args["mode"])); + case "supports_close_for_launch_mode": + return supportsCloseForLaunchMode(_parseLaunchMode(args["mode"])); default: throw Exception("Unknown UrlLauncher method: $name"); } @@ -46,3 +58,34 @@ class UrlLauncherService extends FletService { super.dispose(); } } + +LaunchMode _parseLaunchMode(dynamic value) { + return LaunchMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + LaunchMode.platformDefault; +} + +WebViewConfiguration? _parseWebViewConfiguration(dynamic value) { + if (value is Map) { + var enableJavaScript = parseBool(value["enable_javascript"], true)!; + var enableDomStorage = parseBool(value["enable_dom_storage"], true)!; + var headersValue = value["headers"]; + var headers = headersValue is Map + ? headersValue.map((key, headerValue) => + MapEntry(key.toString(), headerValue.toString())) + : {}; + return WebViewConfiguration( + enableJavaScript: enableJavaScript, + enableDomStorage: enableDomStorage, + headers: headers); + } + return null; +} + +BrowserConfiguration? _parseBrowserConfiguration(dynamic value) { + if (value is Map) { + var showTitle = parseBool(value["show_title"], false)!; + return BrowserConfiguration(showTitle: showTitle); + } + return null; +} diff --git a/packages/flet/lib/src/services/user_accelerometer.dart b/packages/flet/lib/src/services/user_accelerometer.dart new file mode 100644 index 0000000000..8ec5013911 --- /dev/null +++ b/packages/flet/lib/src/services/user_accelerometer.dart @@ -0,0 +1,23 @@ +import 'package:sensors_plus/sensors_plus.dart'; + +import 'base_sensor.dart'; + +class UserAccelerometerService + extends BaseSensorService { + UserAccelerometerService({required super.control}); + + @override + Stream sensorStream(Duration samplingPeriod) { + return userAccelerometerEventStream(samplingPeriod: samplingPeriod); + } + + @override + Map serializeEvent(UserAccelerometerEvent event) { + return { + "x": event.x, + "y": event.y, + "z": event.z, + "timestamp": event.timestamp.microsecondsSinceEpoch, + }; + } +} diff --git a/packages/flet/lib/src/services/wakelock.dart b/packages/flet/lib/src/services/wakelock.dart new file mode 100644 index 0000000000..d1184a22e2 --- /dev/null +++ b/packages/flet/lib/src/services/wakelock.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +import '../flet_service.dart'; + +class WakelockService extends FletService { + WakelockService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("WakelockService(${control.id}).init"); + control.addInvokeMethodListener(_invokeMethod); + } + + Future _invokeMethod(String name, dynamic args) async { + switch (name) { + case "enable": + return WakelockPlus.enable(); + case "disable": + return WakelockPlus.disable(); + case "is_enabled": + return WakelockPlus.enabled; + default: + throw Exception("Unknown Wakelock method: $name"); + } + } + + @override + void dispose() { + debugPrint("WakelockService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_mock.dart b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart index c07caa61f2..1555dd88b7 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_mock.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart @@ -50,8 +50,7 @@ class FletMockBackendChannel implements FletBackendChannel { {"_c": "Text", "_i": 20, "text": "OFF1"} ] }, - "_user_services": {"_c": "ServiceRegistry", "_i": 10, "services": []}, - "_page_services": {"_c": "ServiceRegistry", "_i": 11, "services": []}, + "_services": {"_c": "ServiceRegistry", "_i": 10, "services": []}, "views": [ { "_c": "View", diff --git a/packages/flet/lib/src/utils/launch_url.dart b/packages/flet/lib/src/utils/launch_url.dart index 1cb8385ade..01f2614cfb 100644 --- a/packages/flet/lib/src/utils/launch_url.dart +++ b/packages/flet/lib/src/utils/launch_url.dart @@ -28,30 +28,34 @@ class Url { /// ``` Future openWebBrowser( Url urlObj, { - bool webPopupWindow = false, - String? webPopupWindowName, - int? webPopupWindowWidth, - int? webPopupWindowHeight, + LaunchMode mode = LaunchMode.platformDefault, + WebViewConfiguration? webViewConfiguration, + BrowserConfiguration? browserConfiguration, + String? webOnlyWindowName, }) async { - if (webPopupWindow == true) { - // Open a popup browser window (web only) - openPopupBrowserWindow( - urlObj.url, - webPopupWindowName ?? "Flet", - webPopupWindowWidth ?? 1200, - webPopupWindowHeight ?? 800, - ); - } else { - // Open the URL in the default browser or app - var target = urlObj.target ?? webPopupWindowName; - await launchUrl( - Uri.parse(urlObj.url), - webOnlyWindowName: target, - mode: (target == "_blank") - ? LaunchMode.externalApplication - : LaunchMode.platformDefault, - ); - } + // Open the URL in the default browser or app + var target = urlObj.target ?? webOnlyWindowName; + var resolvedMode = (target == "_blank" && mode == LaunchMode.platformDefault) + ? LaunchMode.externalApplication + : mode; + await launchUrl( + Uri.parse(urlObj.url), + mode: resolvedMode, + webViewConfiguration: webViewConfiguration ?? const WebViewConfiguration(), + browserConfiguration: + browserConfiguration ?? const BrowserConfiguration(), + webOnlyWindowName: target, + ); +} + +Future openWindow( + Url urlObj, { + String? title, + double? width, + double? height, +}) async { + openPopupBrowserWindow(urlObj.url, title ?? "Flet", (width ?? 1200).round(), + (height ?? 800).round()); } Url? parseUrl(dynamic value, [Url? defaultValue]) { diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 996cb72e31..e0f280973f 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -21,8 +21,10 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter + battery_plus: ^7.0.0 collection: ^1.19.0 - device_info_plus: ^12.1.0 + connectivity_plus: ^7.0.0 + device_info_plus: ^12.3.0 equatable: ^2.0.3 file_picker: ^10.3.3 flutter_highlight: 0.7.0 @@ -36,8 +38,10 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.2 screenshot: ^3.0.0 - sensors_plus: ^6.1.1 + screen_brightness: ^2.1.7 + sensors_plus: ^7.0.0 shared_preferences: 2.5.3 + share_plus: ^12.0.1 shimmer: ^3.0.0 url_launcher: 6.3.2 vector_math: ^2.2.0 @@ -45,15 +49,9 @@ dependencies: web_socket_channel: ^3.0.2 window_manager: ^0.5.1 window_to_front: ^0.0.3 + wakelock_plus: ^1.2.8 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.1 - -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg diff --git a/sdk/python/examples/services/accelerometer/basic.py b/sdk/python/examples/services/accelerometer/basic.py new file mode 100644 index 0000000000..e88f09475d --- /dev/null +++ b/sdk/python/examples/services/accelerometer/basic.py @@ -0,0 +1,27 @@ +import flet as ft + + +def main(page: ft.Page): + def handle_reading(e: ft.AccelerometerReadingEvent): + reading.value = f"x={e.x:.2f} m/s^2, y={e.y:.2f} m/s^2, z={e.z:.2f} m/s^2" + page.update() + + def handle_error(e: ft.SensorErrorEvent): + page.add(ft.Text(f"Accelerometer error: {e.message}")) + + page.services.append( + ft.Accelerometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=100), + cancel_on_error=False, + ) + ) + + page.add( + ft.Text("Move your device to see accelerometer readings."), + reading := ft.Text("Waiting for data..."), + ) + + +ft.run(main) diff --git a/sdk/python/examples/controls/audio/declarative_1.py b/sdk/python/examples/services/audio/declarative_1.py similarity index 100% rename from sdk/python/examples/controls/audio/declarative_1.py rename to sdk/python/examples/services/audio/declarative_1.py diff --git a/sdk/python/examples/controls/audio/example_1.py b/sdk/python/examples/services/audio/example_1.py similarity index 99% rename from sdk/python/examples/controls/audio/example_1.py rename to sdk/python/examples/services/audio/example_1.py index f4cb7e58c2..eadef8d451 100644 --- a/sdk/python/examples/controls/audio/example_1.py +++ b/sdk/python/examples/services/audio/example_1.py @@ -1,6 +1,5 @@ -import flet_audio as fta - import flet as ft +import flet_audio as fta def main(page: ft.Page): diff --git a/sdk/python/examples/controls/audio_recorder/example_1.py b/sdk/python/examples/services/audio_recorder/example_1.py similarity index 97% rename from sdk/python/examples/controls/audio_recorder/example_1.py rename to sdk/python/examples/services/audio_recorder/example_1.py index b88cbd03d8..9a0502ec15 100644 --- a/sdk/python/examples/controls/audio_recorder/example_1.py +++ b/sdk/python/examples/services/audio_recorder/example_1.py @@ -1,10 +1,5 @@ -import logging - -import flet_audio_recorder as far - import flet as ft - -logging.basicConfig(level=logging.DEBUG) +import flet_audio_recorder as far def main(page: ft.Page): diff --git a/sdk/python/examples/services/barometer/basic.py b/sdk/python/examples/services/barometer/basic.py new file mode 100644 index 0000000000..5242711a46 --- /dev/null +++ b/sdk/python/examples/services/barometer/basic.py @@ -0,0 +1,26 @@ +import flet as ft + + +def main(page: ft.Page): + def handle_reading(e: ft.BarometerReadingEvent): + reading.value = f"{e.pressure:.2f} hPa" + page.update() + + def handle_error(e: ft.SensorErrorEvent): + page.add(ft.Text(f"Barometer error: {e.message}")) + + page.services.append( + ft.Barometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=500), + ) + ) + + page.add( + ft.Text("Atmospheric pressure (hPa)."), + reading := ft.Text("Waiting for data..."), + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/battery/basic.py b/sdk/python/examples/services/battery/basic.py new file mode 100644 index 0000000000..9b7e83a414 --- /dev/null +++ b/sdk/python/examples/services/battery/basic.py @@ -0,0 +1,43 @@ +import flet as ft + + +async def main(page: ft.Page): + battery = ft.Battery() + page.services.append(battery) # need to keep a reference to the service + + info = ft.Text() + state_text = ft.Text() + + async def refresh_info(_=None): + level = await battery.get_battery_level() + state = await battery.get_battery_state() + save_mode = await battery.is_in_battery_save_mode() + info.value = ( + f"Level: {level}% | State: {state} | " + f"Battery saver: {'on' if save_mode else 'off'}" + ) + + async def on_state_change(e: ft.BatteryStateChangeEvent): + state_text.value = f"State changed: {e.state}" + await refresh_info() + + battery.on_state_change = on_state_change + + await refresh_info() + + page.add( + ft.Column( + [ + info, + ft.Row( + [ + ft.Button("Refresh battery info", on_click=refresh_info), + ] + ), + state_text, + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/browsercontextmenu/basic.py b/sdk/python/examples/services/browsercontextmenu/basic.py new file mode 100644 index 0000000000..f2225a0e14 --- /dev/null +++ b/sdk/python/examples/services/browsercontextmenu/basic.py @@ -0,0 +1,32 @@ +# +# Run this example as a web app using the command: +# +# flet run --web basic.py +# + + +import flet as ft + + +async def main(page: ft.Page): + bcm = ft.BrowserContextMenu() + + async def disable_context_menu(): + await bcm.disable() + + async def enable_context_menu(): + await bcm.enable() + + page.add( + ft.Column( + [ + ft.Button( + "Disable browser context menu", on_click=disable_context_menu + ), + ft.Button("Enable browser context menu", on_click=enable_context_menu), + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/clipboard/basic.py b/sdk/python/examples/services/clipboard/basic.py new file mode 100644 index 0000000000..b7fb09cb92 --- /dev/null +++ b/sdk/python/examples/services/clipboard/basic.py @@ -0,0 +1,25 @@ +import flet as ft + + +async def main(page: ft.Page): + async def set_to_clipboard(): + await ft.Clipboard().set(text_to_copy.value) + text_to_copy.value = "" + page.show_dialog(ft.SnackBar("Text copied to clipboard")) + + async def get_from_clipboard(): + contents = await ft.Clipboard().get() + page.add(ft.Text(f"Clipboard contents: {contents}")) + + page.add( + ft.Column( + [ + text_to_copy := ft.TextField(label="Text to copy"), + ft.Button("Set to clipboard", on_click=set_to_clipboard), + ft.Button("Get from clipboard", on_click=get_from_clipboard), + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/connectivity/basic.py b/sdk/python/examples/services/connectivity/basic.py new file mode 100644 index 0000000000..895c15f1ad --- /dev/null +++ b/sdk/python/examples/services/connectivity/basic.py @@ -0,0 +1,35 @@ +import flet as ft + + +async def main(page: ft.Page): + connectivity = ft.Connectivity() + + status = ft.Text() + changes = ft.Text() + + async def refresh(_=None): + results = await connectivity.get_connectivity() + status.value = "Current connectivity: " + ", ".join(r.value for r in results) + + async def on_change(e: ft.ConnectivityChangeEvent): + changes.value = "Connectivity changed: " + ", ".join( + r.value for r in e.connectivity + ) + await refresh() + + connectivity.on_change = on_change + + await refresh() + + page.add( + ft.Column( + [ + status, + ft.Button("Refresh connectivity", on_click=refresh), + changes, + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/controls/file_picker/media/pick_and_upload.png b/sdk/python/examples/services/file_picker/media/pick_and_upload.png similarity index 100% rename from sdk/python/examples/controls/file_picker/media/pick_and_upload.png rename to sdk/python/examples/services/file_picker/media/pick_and_upload.png diff --git a/sdk/python/examples/controls/file_picker/media/pick_save_and_get_directory_path.png b/sdk/python/examples/services/file_picker/media/pick_save_and_get_directory_path.png similarity index 100% rename from sdk/python/examples/controls/file_picker/media/pick_save_and_get_directory_path.png rename to sdk/python/examples/services/file_picker/media/pick_save_and_get_directory_path.png diff --git a/sdk/python/examples/controls/file_picker/pick_and_upload.py b/sdk/python/examples/services/file_picker/pick_and_upload.py similarity index 95% rename from sdk/python/examples/controls/file_picker/pick_and_upload.py rename to sdk/python/examples/services/file_picker/pick_and_upload.py index a90ca6df2d..aabadd4f0d 100644 --- a/sdk/python/examples/controls/file_picker/pick_and_upload.py +++ b/sdk/python/examples/services/file_picker/pick_and_upload.py @@ -3,7 +3,7 @@ # # Run this example with: # export FLET_SECRET_KEY= -# uv run flet run --web examples/controls/file_picker/pick_and_upload.py +# uv run flet run --web examples/services/file_picker/pick_and_upload.py # from dataclasses import dataclass, field @@ -28,7 +28,7 @@ def main(page: ft.Page): "Run this example with:\n" " export FLET_SECRET_KEY=\n" " flet run --web " - "examples/controls/file_picker/pick_and_upload.py", + "examples/services/file_picker/pick_and_upload.py", color=ft.Colors.RED, selectable=True, ) diff --git a/sdk/python/examples/controls/file_picker/pick_save_and_get_directory_path.py b/sdk/python/examples/services/file_picker/pick_save_and_get_directory_path.py similarity index 100% rename from sdk/python/examples/controls/file_picker/pick_save_and_get_directory_path.py rename to sdk/python/examples/services/file_picker/pick_save_and_get_directory_path.py diff --git a/sdk/python/examples/controls/flashlight/example_1.py b/sdk/python/examples/services/flashlight/example_1.py similarity index 99% rename from sdk/python/examples/controls/flashlight/example_1.py rename to sdk/python/examples/services/flashlight/example_1.py index 2f1e7f3c16..195de66fb4 100644 --- a/sdk/python/examples/controls/flashlight/example_1.py +++ b/sdk/python/examples/services/flashlight/example_1.py @@ -1,6 +1,5 @@ -import flet_flashlight as ffl - import flet as ft +import flet_flashlight as ffl def main(page: ft.Page): diff --git a/sdk/python/examples/controls/geolocator/example_1.py b/sdk/python/examples/services/geolocator/example_1.py similarity index 99% rename from sdk/python/examples/controls/geolocator/example_1.py rename to sdk/python/examples/services/geolocator/example_1.py index 2331d360d2..eef035f3c1 100644 --- a/sdk/python/examples/controls/geolocator/example_1.py +++ b/sdk/python/examples/services/geolocator/example_1.py @@ -1,8 +1,7 @@ from typing import Callable -import flet_geolocator as ftg - import flet as ft +import flet_geolocator as ftg async def main(page: ft.Page): diff --git a/sdk/python/examples/services/gyroscope/basic.py b/sdk/python/examples/services/gyroscope/basic.py new file mode 100644 index 0000000000..a54c9d9b7e --- /dev/null +++ b/sdk/python/examples/services/gyroscope/basic.py @@ -0,0 +1,26 @@ +import flet as ft + + +def main(page: ft.Page): + def handle_reading(e: ft.GyroscopeReadingEvent): + reading.value = f"x={e.x:.2f} rad/s, y={e.y:.2f} rad/s, z={e.z:.2f} rad/s" + page.update() + + def handle_error(e: ft.SensorErrorEvent): + page.add(ft.Text(f"Gyroscope error: {e.message}")) + + page.services.append( + ft.Gyroscope( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=100), + ) + ) + + page.add( + ft.Text("Rotate your device to see gyroscope readings."), + reading := ft.Text("Waiting for data..."), + ) + + +ft.run(main) diff --git a/sdk/python/examples/controls/haptic_feedback/basic.py b/sdk/python/examples/services/haptic_feedback/basic.py similarity index 100% rename from sdk/python/examples/controls/haptic_feedback/basic.py rename to sdk/python/examples/services/haptic_feedback/basic.py diff --git a/sdk/python/examples/services/magnetometer/basic.py b/sdk/python/examples/services/magnetometer/basic.py new file mode 100644 index 0000000000..5790dc85ae --- /dev/null +++ b/sdk/python/examples/services/magnetometer/basic.py @@ -0,0 +1,26 @@ +import flet as ft + + +def main(page: ft.Page): + def handle_reading(e: ft.MagnetometerReadingEvent): + reading.value = f"x={e.x:.2f} uT, y={e.y:.2f} uT, z={e.z:.2f} uT" + page.update() + + def handle_error(e: ft.SensorErrorEvent): + page.add(ft.Text(f"Magnetometer error: {e.message}")) + + page.services.append( + ft.Magnetometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=200), + ) + ) + + page.add( + ft.Text("Monitor the ambient magnetic field (uT)."), + reading := ft.Text("Waiting for data..."), + ) + + +ft.run(main) diff --git a/sdk/python/examples/controls/permission_handler/example_1.py b/sdk/python/examples/services/permission_handler/example_1.py similarity index 99% rename from sdk/python/examples/controls/permission_handler/example_1.py rename to sdk/python/examples/services/permission_handler/example_1.py index 534431b596..44188a3231 100644 --- a/sdk/python/examples/controls/permission_handler/example_1.py +++ b/sdk/python/examples/services/permission_handler/example_1.py @@ -1,6 +1,5 @@ -import flet_permission_handler as fph - import flet as ft +import flet_permission_handler as fph def main(page: ft.Page): diff --git a/sdk/python/examples/services/screen_brightness/basic.py b/sdk/python/examples/services/screen_brightness/basic.py new file mode 100644 index 0000000000..c89585ad4f --- /dev/null +++ b/sdk/python/examples/services/screen_brightness/basic.py @@ -0,0 +1,98 @@ +import flet as ft + + +async def main(page: ft.Page): + sb = ft.ScreenBrightness() + page.services.append(sb) + + info = ft.Text() + system_change = ft.Text() + app_change = ft.Text() + + level = ft.Slider(min=0, max=1, divisions=20, value=0.5, label="Brightness") + animate_switch = ft.Switch( + label="Animate application changes", value=True, on_change=None + ) + auto_reset_switch = ft.Switch( + label="Auto-reset application brightness on lifecycle changes", + value=True, + on_change=None, + ) + + async def refresh_info(): + system_brightness = await sb.get_system_screen_brightness() + app_brightness = await sb.get_application_screen_brightness() + can_change_system = await sb.can_change_system_screen_brightness() + + animate_switch.value = await sb.is_animate() + auto_reset_switch.value = await sb.is_auto_reset() + + info.value = ( + f"System: {system_brightness:.2f} | " + f"Application: {app_brightness:.2f} | " + f"Can change system: {can_change_system}" + ) + + async def set_application_brightness(_): + await sb.set_application_screen_brightness(level.value) + await refresh_info() + + async def set_system_brightness(_): + await sb.set_system_screen_brightness(level.value) + await refresh_info() + + async def reset_application_brightness(_): + await sb.reset_application_screen_brightness() + await refresh_info() + + async def toggle_animate(e: ft.Event[ft.Switch]): + await sb.set_animate(e.control.value) + await refresh_info() + + async def toggle_auto_reset(e: ft.Event[ft.Switch]): + await sb.set_auto_reset(e.control.value) + await refresh_info() + + async def on_system_change(e: ft.ScreenBrightnessChangeEvent): + system_change.value = f"System brightness changed: {e.brightness:.2f}" + + async def on_application_change(e: ft.ScreenBrightnessChangeEvent): + app_change.value = f"Application brightness changed: {e.brightness:.2f}" + + sb.on_system_screen_brightness_change = on_system_change + sb.on_application_screen_brightness_change = on_application_change + + await refresh_info() + + animate_switch.on_change = toggle_animate + auto_reset_switch.on_change = toggle_auto_reset + + page.add( + ft.Column( + [ + info, + level, + ft.Row( + [ + ft.Button( + "Set application", on_click=set_application_brightness + ), + ft.Button("Set system", on_click=set_system_brightness), + ft.TextButton( + "Reset application", on_click=reset_application_brightness + ), + ], + wrap=True, + ), + animate_switch, + auto_reset_switch, + ft.Divider(), + system_change, + app_change, + ], + spacing=12, + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/controls/semantics_service/accessibility_features.py b/sdk/python/examples/services/semantics_service/accessibility_features.py similarity index 100% rename from sdk/python/examples/controls/semantics_service/accessibility_features.py rename to sdk/python/examples/services/semantics_service/accessibility_features.py diff --git a/sdk/python/examples/controls/shake_detector/basic.py b/sdk/python/examples/services/shake_detector/basic.py similarity index 100% rename from sdk/python/examples/controls/shake_detector/basic.py rename to sdk/python/examples/services/shake_detector/basic.py diff --git a/sdk/python/examples/services/share/basic.py b/sdk/python/examples/services/share/basic.py new file mode 100644 index 0000000000..f600389ccd --- /dev/null +++ b/sdk/python/examples/services/share/basic.py @@ -0,0 +1,83 @@ +import os + +import flet as ft + + +async def main(page: ft.Page): + share = ft.Share() + + status = ft.Text() + result_raw = ft.Text() + + async def do_share_text(): + result = await share.share_text( + "Hello from Flet!", + subject="Greeting", + title="Share greeting", + ) + status.value = f"Share status: {result.status}" + result_raw.value = f"Raw: {result.raw}" + + async def do_share_uri(): + result = await share.share_uri("https://flet.dev") + status.value = f"Share status: {result.status}" + result_raw.value = f"Raw: {result.raw}" + + async def do_share_files_from_bytes(): + file = ft.ShareFile.from_bytes( + b"Sample content from memory", + mime_type="text/plain", + name="sample.txt", + ) + result = await share.share_files( + [file], + text="Sharing a file from memory", + ) + status.value = f"Share status: {result.status}" + result_raw.value = f"Raw: {result.raw}" + + async def do_share_files_from_paths(): + if page.web: + status.value = "File sharing from paths is not supported on the web." + return + # + temp_dir = await ft.StoragePaths().get_temporary_directory() + file_path = os.path.join(temp_dir, "sample_from_path.txt") + with open(file_path, "wb") as f: + f.write(b"Sample content from file path") + + result = await share.share_files( + [ft.ShareFile.from_path(file_path)], + text="Sharing a file from memory", + ) + status.value = f"Share status: {result.status}" + result_raw.value = f"Raw: {result.raw}" + + page.add( + ft.SafeArea( + ft.Column( + [ + ft.Row( + [ + ft.Button("Share text", on_click=do_share_text), + ft.Button("Share link", on_click=do_share_uri), + ft.Button( + "Share file from bytes", + on_click=do_share_files_from_bytes, + ), + ft.Button( + "Share file from path", + on_click=do_share_files_from_paths, + ), + ], + wrap=True, + ), + status, + result_raw, + ], + ) + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/sharedpreferences/basic.py b/sdk/python/examples/services/sharedpreferences/basic.py new file mode 100644 index 0000000000..4222b97cd2 --- /dev/null +++ b/sdk/python/examples/services/sharedpreferences/basic.py @@ -0,0 +1,37 @@ +import flet as ft + + +async def main(page: ft.Page): + async def set_value(): + await ft.SharedPreferences().set(store_key.value, store_value.value) + get_key.value = store_key.value + store_key.value = "" + store_value.value = "" + page.show_dialog(ft.SnackBar("Value saved to SharedPreferences")) + + async def get_value(): + contents = await ft.SharedPreferences().get(get_key.value) + page.add(ft.Text(f"SharedPreferences contents: {contents}")) + + page.add( + ft.Column( + [ + ft.Row( + [ + store_key := ft.TextField(label="Key"), + store_value := ft.TextField(label="Value"), + ft.Button("Set", on_click=set_value), + ] + ), + ft.Row( + [ + get_key := ft.TextField(label="Key"), + ft.Button("Get", on_click=get_value), + ] + ), + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/storagepaths/basic.py b/sdk/python/examples/services/storagepaths/basic.py new file mode 100644 index 0000000000..d9ab1e06e3 --- /dev/null +++ b/sdk/python/examples/services/storagepaths/basic.py @@ -0,0 +1,55 @@ +import flet as ft + + +async def main(page: ft.Page): + storage_paths = ft.StoragePaths() + + items = [] + for label, method in [ + ("Application cache directory", storage_paths.get_application_cache_directory), + ( + "Application documents directory", + storage_paths.get_application_documents_directory, + ), + ( + "Application support directory", + storage_paths.get_application_support_directory, + ), + ("Downloads directory", storage_paths.get_downloads_directory), + ("External cache directories", storage_paths.get_external_cache_directories), + ( + "External storage directories", + storage_paths.get_external_storage_directories, + ), + ("Library directory", storage_paths.get_library_directory), + ("External storage directory", storage_paths.get_external_storage_directory), + ("Temporary directory", storage_paths.get_temporary_directory), + ("Console log filename", storage_paths.get_console_log_filename), + ]: + try: + value = await method() + except ft.FletUnsupportedPlatformException as e: + value = f"Not supported: {e}" + except Exception as e: + value = f"Error: {e}" + else: + if isinstance(value, list): + value = ", ".join(value) + elif value is None: + value = "Unavailable" + + items.append( + ft.Text( + spans=[ + ft.TextSpan( + f"{label}: ", style=ft.TextStyle(weight=ft.FontWeight.BOLD) + ), + ft.TextSpan(value), + ] + ) + ) + + page.add(ft.Column(items, spacing=5)) + + +ft.run(main) diff --git a/sdk/python/examples/services/urllauncher/basic.py b/sdk/python/examples/services/urllauncher/basic.py new file mode 100644 index 0000000000..874cfa3551 --- /dev/null +++ b/sdk/python/examples/services/urllauncher/basic.py @@ -0,0 +1,81 @@ +import flet as ft + + +async def main(page: ft.Page): + url_launcher = ft.UrlLauncher() + + url = ft.TextField(label="URL to open", value="https://flet.dev", expand=True) + status = ft.Text() + + async def can_launch(): + can = await url_launcher.can_launch_url(url.value) + status.value = f"Can launch: {can}" + + async def launch_default(): + await url_launcher.launch_url(url.value) + + async def launch_in_app_webview(): + await url_launcher.launch_url( + url.value, + mode=ft.LaunchMode.IN_APP_WEB_VIEW, + web_view_configuration=ft.WebViewConfiguration( + enable_javascript=True, enable_dom_storage=True + ), + ) + + async def launch_in_app_browser_view(): + await url_launcher.launch_url( + url.value, + mode=ft.LaunchMode.IN_APP_BROWSER_VIEW, + browser_configuration=ft.BrowserConfiguration(show_title=True), + ) + + async def launch_external(): + await url_launcher.launch_url( + url.value, + mode=ft.LaunchMode.EXTERNAL_APPLICATION, + web_only_window_name="_blank", + ) + + async def launch_popup(): + await url_launcher.open_window( + url.value, title="Flet popup", width=480, height=640 + ) + + async def close_webview(_): + supported = await url_launcher.supports_close_for_launch_mode( + ft.LaunchMode.IN_APP_WEB_VIEW + ) + if supported: + await url_launcher.close_in_app_web_view() + else: + status.value = "Close in-app web view not supported on this platform" + + page.add( + ft.Column( + [ + url, + ft.Row( + [ + ft.Button("Launch URL", on_click=launch_default), + ft.Button( + "Launch in-app webview", on_click=launch_in_app_webview + ), + ft.Button( + "Launch in-app browser view", + on_click=launch_in_app_browser_view, + ), + ft.Button("Launch external/new tab", on_click=launch_external), + ft.Button("Open popup window (web)", on_click=launch_popup), + ft.Button("Can launch?", on_click=can_launch), + ft.Button("Close in-app webview", on_click=close_webview), + ], + wrap=True, + ), + status, + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/user_accelerometer/basic.py b/sdk/python/examples/services/user_accelerometer/basic.py new file mode 100644 index 0000000000..db6067e7e3 --- /dev/null +++ b/sdk/python/examples/services/user_accelerometer/basic.py @@ -0,0 +1,29 @@ +import flet as ft + + +def main(page: ft.Page): + def handle_reading(e: ft.UserAccelerometerReadingEvent): + reading.value = f"x={e.x:.2f} m/s^2, y={e.y:.2f} m/s^2, z={e.z:.2f} m/s^2" + page.update() + + def handle_error(e: ft.SensorErrorEvent): + page.add(ft.Text(f"UserAccelerometer error: {e.message}")) + + page.services.append( + ft.UserAccelerometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=100), + ) + ) + + page.add( + ft.Text( + "Linear acceleration without gravity. " + "Keep the app running on a device with motion sensors." + ), + reading := ft.Text("Waiting for data..."), + ) + + +ft.run(main) diff --git a/sdk/python/examples/services/wakelock/basic.py b/sdk/python/examples/services/wakelock/basic.py new file mode 100644 index 0000000000..1adf678d6b --- /dev/null +++ b/sdk/python/examples/services/wakelock/basic.py @@ -0,0 +1,39 @@ +import flet as ft + + +async def main(page: ft.Page): + wakelock = ft.Wakelock() + + status = ft.Text() + + async def update_status(): + enabled = await wakelock.is_enabled() + status.value = f"Wakelock enabled: {enabled}" + + async def enable_lock(): + await wakelock.enable() + await update_status() + + async def disable_lock(): + await wakelock.disable() + await update_status() + + await update_status() + + page.add( + ft.Column( + [ + status, + ft.Row( + [ + ft.Button("Enable wakelock", on_click=enable_lock), + ft.Button("Disable wakelock", on_click=disable_lock), + ], + wrap=True, + ), + ], + ) + ) + + +ft.run(main) diff --git a/sdk/python/packages/flet-audio-recorder/README.md b/sdk/python/packages/flet-audio-recorder/README.md index bdade8c29f..daaebde630 100644 --- a/sdk/python/packages/flet-audio-recorder/README.md +++ b/sdk/python/packages/flet-audio-recorder/README.md @@ -41,4 +41,4 @@ To install the `flet-audio-recorder` package and add it to your project dependen ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/audio_recorder). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/services/audio_recorder). diff --git a/sdk/python/packages/flet-audio/README.md b/sdk/python/packages/flet-audio/README.md index 04468e92b0..05a44b2ac0 100644 --- a/sdk/python/packages/flet-audio/README.md +++ b/sdk/python/packages/flet-audio/README.md @@ -48,4 +48,4 @@ To install the `flet-audio` package and add it to your project dependencies: ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/audio). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/services/audio). diff --git a/sdk/python/packages/flet-flashlight/README.md b/sdk/python/packages/flet-flashlight/README.md index 7cf567f7e2..fb165f33e7 100644 --- a/sdk/python/packages/flet-flashlight/README.md +++ b/sdk/python/packages/flet-flashlight/README.md @@ -39,4 +39,4 @@ To install the `flet-flashlight` package and add it to your project dependencies ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/examples/controls/flashlight). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/examples/services/flashlight). diff --git a/sdk/python/packages/flet-geolocator/README.md b/sdk/python/packages/flet-geolocator/README.md index 37c2bf6aa7..d670912e13 100644 --- a/sdk/python/packages/flet-geolocator/README.md +++ b/sdk/python/packages/flet-geolocator/README.md @@ -43,4 +43,4 @@ To install the `flet-geolocator` package and add it to your project dependencies ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/examples/controls/geolocator). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/examples/services/geolocator). diff --git a/sdk/python/packages/flet-map/src/flet_map/types.py b/sdk/python/packages/flet-map/src/flet_map/types.py index 90418851ae..bcb7d1f7c3 100644 --- a/sdk/python/packages/flet-map/src/flet_map/types.py +++ b/sdk/python/packages/flet-map/src/flet_map/types.py @@ -185,7 +185,7 @@ class DashedStrokePattern(StrokePattern): values must be strictly positive. 'Units' refers to pixels, unless the pattern has been scaled due to the - use of [`pattern_fit`][(c).] [`PatternFit.SCALE_UP`][flet.]. + use of [`pattern_fit`][(c).] [`PatternFit.SCALE_UP`][flet.PatternFit.SCALE_UP]. If more than two items are specified, then each segments will alternate/iterate through the values. diff --git a/sdk/python/packages/flet-permission-handler/README.md b/sdk/python/packages/flet-permission-handler/README.md index d8a4dc0875..adda7ab260 100644 --- a/sdk/python/packages/flet-permission-handler/README.md +++ b/sdk/python/packages/flet-permission-handler/README.md @@ -42,4 +42,4 @@ To install the `flet-permission-handler` package and add it to your project depe ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/permission_handler). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/services/permission_handler). diff --git a/sdk/python/packages/flet/docs/api-reference/index.md b/sdk/python/packages/flet/docs/api-reference/index.md new file mode 100644 index 0000000000..8d9e5a61f2 --- /dev/null +++ b/sdk/python/packages/flet/docs/api-reference/index.md @@ -0,0 +1,8 @@ +# API Reference + +- [Controls](../controls/index.md) - UI building blocks with properties, events, and usage examples. +- [Services](../services/index.md) - Device and platform capabilities such as sensors, storage, and permissions. +- [CLI](../cli/index.md) - `flet` commands for creating, running, packaging, and debugging apps. +- [Types](../types/index.md) - Core types, enums, events, exceptions, and utilities shared across the SDK. +- [Built-in binary Python packages for Android and iOS](../reference/binary-packages-android-ios.md) - Prebuilt wheels available from `https://pypi.flet.dev` for mobile targets. +- [Environment Variables](../reference/environment-variables.md) - Runtime configuration toggles for apps and the CLI. diff --git a/sdk/python/packages/flet/docs/audio/index.md b/sdk/python/packages/flet/docs/audio/index.md index 26c480c827..5a02bd3015 100644 --- a/sdk/python/packages/flet/docs/audio/index.md +++ b/sdk/python/packages/flet/docs/audio/index.md @@ -1,6 +1,6 @@ --- class_name: flet_audio.Audio -examples: ../../examples/controls/audio +examples: ../../examples/services/audio --- # Audio diff --git a/sdk/python/packages/flet/docs/audio_recorder/index.md b/sdk/python/packages/flet/docs/audio_recorder/index.md index 3755780964..319e407891 100644 --- a/sdk/python/packages/flet/docs/audio_recorder/index.md +++ b/sdk/python/packages/flet/docs/audio_recorder/index.md @@ -1,6 +1,6 @@ --- class_name: flet_audio_recorder.AudioRecorder -examples: ../../examples/controls/audio_recorder +examples: ../../examples/services/audio_recorder --- # Audio Recorder diff --git a/sdk/python/packages/flet/docs/cookbook/file-picker-and-uploads.md b/sdk/python/packages/flet/docs/cookbook/file-picker-and-uploads.md index c64de1a695..45954de896 100644 --- a/sdk/python/packages/flet/docs/cookbook/file-picker-and-uploads.md +++ b/sdk/python/packages/flet/docs/cookbook/file-picker-and-uploads.md @@ -2,10 +2,10 @@ It works on all platforms: Web, macOS, Window, Linux, iOS and Android. -{{ image("../examples/controls/file_picker/media/pick_save_and_get_directory_path.png", alt="File picker all modes demo", width="80%") }} +{{ image("../examples/services/file_picker/media/pick_save_and_get_directory_path.png", alt="File picker all modes demo", width="80%") }} -Check out [source code of the demo above](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/controls/file-picker/pick-save-and-get-directory-path.py). +Check out [source code of the demo above](https://github.com/flet-dev/flet/blob/main/sdk/python/examples/services/file_picker/pick_save_and_get_directory_path.py). File picker allows opening three dialogs: @@ -159,10 +159,10 @@ reported for every 10% uploaded. The following example demonstrates multiple file uploads: ```python ---8<-- "../../examples/controls/file_picker/pick_and_upload.py" +--8<-- "../../examples/services/file_picker/pick_and_upload.py" ``` -{{ image("../examples/controls/file_picker/media/pick_and_upload.png", alt="File picker multiple uploads", width="80%") }} +{{ image("../examples/services/file_picker/media/pick_and_upload.png", alt="File picker multiple uploads", width="80%") }} See [`FilePicker`][flet.FilePicker] control docs for all its properties and examples. diff --git a/sdk/python/packages/flet/docs/cookbook/index.md b/sdk/python/packages/flet/docs/cookbook/index.md index dc9e927625..a5b2711690 100644 --- a/sdk/python/packages/flet/docs/cookbook/index.md +++ b/sdk/python/packages/flet/docs/cookbook/index.md @@ -1,4 +1,5 @@ # Cookbook -This cookbook contains recipes that demonstrate how to solve common problems while writing Flet apps. -Each recipe is self-contained and can be used as a reference to help you build up an application. +The cookbook contains recipes that demonstrate how to solve common problems while writing Flet apps. Each recipe is self-contained and can be used as a reference to help you build up an application. + +{{ cookbook_overview() }} diff --git a/sdk/python/packages/flet/docs/extras/macros/__init__.py b/sdk/python/packages/flet/docs/extras/macros/__init__.py index 9c84997353..5d03b79169 100644 --- a/sdk/python/packages/flet/docs/extras/macros/__init__.py +++ b/sdk/python/packages/flet/docs/extras/macros/__init__.py @@ -2,7 +2,7 @@ from urllib.parse import urlparse from .cli_to_md import render_flet_cli_as_markdown -from .controls_overview import render_controls_overview +from .controls_overview import render_nav_overview, render_sub_nav_overview def define_env(env): @@ -129,4 +129,16 @@ def flet_cli_as_markdown(command: str = "", subcommands_only: bool = True): @env.macro def controls_overview(): - return render_controls_overview() + return render_sub_nav_overview("Controls") + + @env.macro + def services_overview(): + return render_sub_nav_overview("Services") + + @env.macro + def cookbook_overview(): + return render_nav_overview( + ["Cookbook"], + base_dir="cookbook", + skip_paths={"cookbook/index.md"}, + ) diff --git a/sdk/python/packages/flet/docs/extras/macros/controls_overview.py b/sdk/python/packages/flet/docs/extras/macros/controls_overview.py index dbdc1922d1..744822d6d2 100644 --- a/sdk/python/packages/flet/docs/extras/macros/controls_overview.py +++ b/sdk/python/packages/flet/docs/extras/macros/controls_overview.py @@ -5,19 +5,18 @@ from typing import Any CONTROLS_INDEX_PATH = "controls/index.md" -CONTROLS_DIR = os.path.dirname(CONTROLS_INDEX_PATH) -SKIP_PATHS = {CONTROLS_INDEX_PATH} +SERVICES_INDEX_PATH = "services/index.md" -def _relative_markdown_path(path: str) -> str: - """Return a path relative to the controls index for stable links.""" - base = CONTROLS_DIR or "." +def _relative_markdown_path(path: str, base_dir: str) -> str: + """Return a path relative to the provided index for stable links.""" + base = base_dir or "." return os.path.relpath(path, base).replace(os.sep, "/") -def _build_link(target: str, title: str) -> str: - """Render a Markdown link for a control entry.""" - return f"[`{title}`]({_relative_markdown_path(target)})" +def _build_link(target: str, title: str, base_dir: str) -> str: + """Render a Markdown link for a nav entry.""" + return f"[`{title}`]({_relative_markdown_path(target, base_dir)})" def _find_nav_branch(items: list[Any] | None, label: str): @@ -29,7 +28,9 @@ def _find_nav_branch(items: list[Any] | None, label: str): return None -def _partition_section_entries(entries: list[Any]) -> tuple[str | None, list[Any]]: +def _partition_section_entries( + entries: list[Any], skip_paths: set[str] +) -> tuple[str | None, list[Any]]: """Return (overview_path, child_entries) for a nav section. MkDocs lets sections include raw file paths (treated as implicit "Overview" @@ -41,7 +42,7 @@ def _partition_section_entries(entries: list[Any]) -> tuple[str | None, list[Any remaining: list[Any] = [] for item in entries: if isinstance(item, str): - if overview_path is None and item not in SKIP_PATHS: + if overview_path is None and item not in skip_paths: overview_path = item continue elif isinstance(item, dict): @@ -51,7 +52,7 @@ def _partition_section_entries(entries: list[Any]) -> tuple[str | None, list[Any if ( isinstance(value, str) and title.casefold() == "overview" - and value not in SKIP_PATHS + and value not in skip_paths ): overview_path = value used_for_overview = True @@ -62,7 +63,7 @@ def _partition_section_entries(entries: list[Any]) -> tuple[str | None, list[Any return overview_path, remaining -def _build_nav_nodes(entries: list[Any] | None): +def _build_nav_nodes(entries: list[Any] | None, skip_paths: set[str]): """Convert MkDocs nav entries into a tree of dict nodes.""" nodes: list[dict[str, Any]] = [] for entry in entries or []: @@ -75,19 +76,23 @@ def _build_nav_nodes(entries: list[Any] | None): if isinstance(value, list): # Sections are represented as lists; grab their overview # link (if any) and continue recursively with the rest. - overview_path, remainder = _partition_section_entries(value) - children = _build_nav_nodes(remainder) + overview_path, remainder = _partition_section_entries( + value, skip_paths + ) + children = _build_nav_nodes(remainder, skip_paths) nodes.append( {"title": title, "path": overview_path, "children": children} ) else: - if value in SKIP_PATHS: + if value in skip_paths: continue nodes.append({"title": title, "path": value, "children": []}) return nodes -def _format_nav_list(nodes: list[dict[str, Any]], depth: int = 0) -> list[str]: +def _format_nav_list( + nodes: list[dict[str, Any]], base_dir: str, depth: int = 0 +) -> list[str]: """Render the navigation nodes as a simple Markdown list.""" lines: list[str] = [] indent = " " * (depth * 4) # 4 spaces per level to keep Markdown happy @@ -95,29 +100,61 @@ def _format_nav_list(nodes: list[dict[str, Any]], depth: int = 0) -> list[str]: children = node.get("children") or [] path = node.get("path") title = node["title"] - label = _build_link(path, title) if path else f"**{title}**" + label = _build_link(path, title, base_dir) if path else f"**{title}**" lines.append(f"{indent}- {label}") if children: - lines.extend(_format_nav_list(children, depth + 1)) + lines.extend(_format_nav_list(children, base_dir, depth + 1)) return lines -def render_controls_overview() -> str: - """Produce the controls overview section from mkdocs.yml.""" +def _find_nav_branch_for_path(nav_items: list[Any], nav_path: list[str]): + items = nav_items + for label in nav_path: + items = _find_nav_branch(items, label) + if items is None: + return None + return items + + +def render_nav_overview( + nav_path: list[str], + *, + base_dir: str, + skip_paths: set[str] | None = None, +) -> str: + """Produce an overview list for the requested nav path from mkdocs.yml.""" from mkdocs.config import load_config docs_root = Path(__file__).resolve().parents[3] config = load_config(str(docs_root / "mkdocs.yml")) nav_items = config.get("nav") or [] - api_reference = _find_nav_branch(nav_items, "API Reference") - controls = _find_nav_branch(api_reference, "Controls") if api_reference else None - nodes = _build_nav_nodes(controls) if isinstance(controls, list) else [] + branch = _find_nav_branch_for_path(nav_items, nav_path) + nodes = ( + _build_nav_nodes(branch, skip_paths or set()) + if isinstance(branch, list) + else [] + ) if not nodes: return "" - lines = _format_nav_list(nodes) + lines = _format_nav_list(nodes, base_dir) return "\n".join(lines) + "\n" +def render_sub_nav_overview(nav_name: str) -> str: + """Produce the controls/services overview section from mkdocs.yml.""" + return render_nav_overview( + ["API Reference", nav_name], + base_dir=nav_name.lower(), + skip_paths={CONTROLS_INDEX_PATH, SERVICES_INDEX_PATH}, + ) + + if __name__ == "__main__": - print(render_controls_overview()) + print( + render_nav_overview( + ["API Reference", "Controls"], + base_dir="controls", + skip_paths={CONTROLS_INDEX_PATH, SERVICES_INDEX_PATH}, + ) + ) diff --git a/sdk/python/packages/flet/docs/flashlight/index.md b/sdk/python/packages/flet/docs/flashlight/index.md index 1d67f516ae..fc776bf480 100644 --- a/sdk/python/packages/flet/docs/flashlight/index.md +++ b/sdk/python/packages/flet/docs/flashlight/index.md @@ -1,6 +1,6 @@ --- class_name: flet_flashlight.Flashlight -examples: ../../examples/controls/flashlight +examples: ../../examples/services/flashlight --- # Flashlight diff --git a/sdk/python/packages/flet/docs/geolocator/index.md b/sdk/python/packages/flet/docs/geolocator/index.md index 6e43890908..1cf5c35287 100644 --- a/sdk/python/packages/flet/docs/geolocator/index.md +++ b/sdk/python/packages/flet/docs/geolocator/index.md @@ -1,6 +1,6 @@ --- class_name: flet_geolocator.Geolocator -examples: ../../examples/controls/geolocator +examples: ../../examples/services/geolocator --- # Geolocator diff --git a/sdk/python/packages/flet/docs/permission_handler/index.md b/sdk/python/packages/flet/docs/permission_handler/index.md index c3c5d8b4c6..ca85dfa40a 100644 --- a/sdk/python/packages/flet/docs/permission_handler/index.md +++ b/sdk/python/packages/flet/docs/permission_handler/index.md @@ -1,6 +1,6 @@ --- class_name: flet_permission_handler.PermissionHandler -examples: ../../examples/controls/permission_handler +examples: ../../examples/services/permission_handler --- # Permission Handler diff --git a/sdk/python/packages/flet/docs/services/accelerometer.md b/sdk/python/packages/flet/docs/services/accelerometer.md new file mode 100644 index 0000000000..fc9c0e01a2 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/accelerometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Accelerometer +examples: ../../examples/services/accelerometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/barometer.md b/sdk/python/packages/flet/docs/services/barometer.md new file mode 100644 index 0000000000..dec579306d --- /dev/null +++ b/sdk/python/packages/flet/docs/services/barometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Barometer +examples: ../../examples/services/barometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/battery.md b/sdk/python/packages/flet/docs/services/battery.md new file mode 100644 index 0000000000..3384cfeb77 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/battery.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Battery +examples: ../../examples/services/battery +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/browsercontextmenu.md b/sdk/python/packages/flet/docs/services/browsercontextmenu.md new file mode 100644 index 0000000000..a0d82b85b5 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/browsercontextmenu.md @@ -0,0 +1,14 @@ +--- +class_name: flet.BrowserContextMenu +examples: ../../examples/services/browsercontextmenu +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/clipboard.md b/sdk/python/packages/flet/docs/services/clipboard.md new file mode 100644 index 0000000000..c8660f7857 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/clipboard.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Clipboard +examples: ../../examples/services/clipboard +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/connectivity.md b/sdk/python/packages/flet/docs/services/connectivity.md new file mode 100644 index 0000000000..8de0f503ac --- /dev/null +++ b/sdk/python/packages/flet/docs/services/connectivity.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Connectivity +examples: ../../examples/services/connectivity +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/filepicker.md b/sdk/python/packages/flet/docs/services/filepicker.md similarity index 84% rename from sdk/python/packages/flet/docs/controls/filepicker.md rename to sdk/python/packages/flet/docs/services/filepicker.md index 13521c33b2..43e72fda8e 100644 --- a/sdk/python/packages/flet/docs/controls/filepicker.md +++ b/sdk/python/packages/flet/docs/services/filepicker.md @@ -1,7 +1,7 @@ --- class_name: flet.FilePicker -examples: ../../examples/controls/file_picker -example_images: ../examples/controls/file_picker/media +examples: ../../examples/services/file_picker +example_images: ../examples/services/file_picker/media --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/gyroscope.md b/sdk/python/packages/flet/docs/services/gyroscope.md new file mode 100644 index 0000000000..3fe566142c --- /dev/null +++ b/sdk/python/packages/flet/docs/services/gyroscope.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Gyroscope +examples: ../../examples/services/gyroscope +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/hapticfeedback.md b/sdk/python/packages/flet/docs/services/hapticfeedback.md similarity index 78% rename from sdk/python/packages/flet/docs/controls/hapticfeedback.md rename to sdk/python/packages/flet/docs/services/hapticfeedback.md index 82b074a625..2a43e26a0f 100644 --- a/sdk/python/packages/flet/docs/controls/hapticfeedback.md +++ b/sdk/python/packages/flet/docs/services/hapticfeedback.md @@ -1,6 +1,6 @@ --- class_name: flet.HapticFeedback -examples: ../../examples/controls/haptic_feedback +examples: ../../examples/services/haptic_feedback --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/index.md b/sdk/python/packages/flet/docs/services/index.md new file mode 100644 index 0000000000..37a2633611 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/index.md @@ -0,0 +1,5 @@ +# Services + +Browse the complete catalog of services available in Flet. + +{{ services_overview() }} diff --git a/sdk/python/packages/flet/docs/services/magnetometer.md b/sdk/python/packages/flet/docs/services/magnetometer.md new file mode 100644 index 0000000000..bb005ad90b --- /dev/null +++ b/sdk/python/packages/flet/docs/services/magnetometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Magnetometer +examples: ../../examples/services/magnetometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/screenbrightness.md b/sdk/python/packages/flet/docs/services/screenbrightness.md new file mode 100644 index 0000000000..236fb9b8ff --- /dev/null +++ b/sdk/python/packages/flet/docs/services/screenbrightness.md @@ -0,0 +1,14 @@ +--- +class_name: flet.ScreenBrightness +examples: ../../examples/services/screen_brightness +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/semanticsservice.md b/sdk/python/packages/flet/docs/services/semanticsservice.md similarity index 81% rename from sdk/python/packages/flet/docs/controls/semanticsservice.md rename to sdk/python/packages/flet/docs/services/semanticsservice.md index e2b2f46ad5..5eac64f5a5 100644 --- a/sdk/python/packages/flet/docs/controls/semanticsservice.md +++ b/sdk/python/packages/flet/docs/services/semanticsservice.md @@ -1,6 +1,6 @@ --- class_name: flet.SemanticsService -examples: ../../examples/controls/semantics_service +examples: ../../examples/services/semantics_service --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/shakedetector.md b/sdk/python/packages/flet/docs/services/shakedetector.md similarity index 79% rename from sdk/python/packages/flet/docs/controls/shakedetector.md rename to sdk/python/packages/flet/docs/services/shakedetector.md index e5de549874..8a2759fe08 100644 --- a/sdk/python/packages/flet/docs/controls/shakedetector.md +++ b/sdk/python/packages/flet/docs/services/shakedetector.md @@ -1,6 +1,6 @@ --- class_name: flet.ShakeDetector -examples: ../../examples/controls/shake_detector +examples: ../../examples/services/shake_detector --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/share.md b/sdk/python/packages/flet/docs/services/share.md new file mode 100644 index 0000000000..11f472ac3a --- /dev/null +++ b/sdk/python/packages/flet/docs/services/share.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Share +examples: ../../examples/services/share +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/sharedpreferences.md b/sdk/python/packages/flet/docs/services/sharedpreferences.md new file mode 100644 index 0000000000..a3e4350229 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/sharedpreferences.md @@ -0,0 +1,16 @@ +--- +class_name: flet.SharedPreferences +examples: ../../examples/services/sharedpreferences +--- + +{{ class_summary(class_name) }} + +## Examples + +### Basic Example + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/storagepaths.md b/sdk/python/packages/flet/docs/services/storagepaths.md new file mode 100644 index 0000000000..7166597d51 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/storagepaths.md @@ -0,0 +1,16 @@ +--- +class_name: flet.StoragePaths +examples: ../../examples/services/storagepaths +--- + +{{ class_summary(class_name) }} + +## Examples + +### Basic Example + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/urllauncher.md b/sdk/python/packages/flet/docs/services/urllauncher.md new file mode 100644 index 0000000000..691ce990c0 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/urllauncher.md @@ -0,0 +1,16 @@ +--- +class_name: flet.UrlLauncher +examples: ../../examples/services/urllauncher +--- + +{{ class_summary(class_name) }} + +## Examples + +### Basic Example + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/useraccelerometer.md b/sdk/python/packages/flet/docs/services/useraccelerometer.md new file mode 100644 index 0000000000..ab46a5cb3a --- /dev/null +++ b/sdk/python/packages/flet/docs/services/useraccelerometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.UserAccelerometer +examples: ../../examples/services/user_accelerometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/wakelock.md b/sdk/python/packages/flet/docs/services/wakelock.md new file mode 100644 index 0000000000..a232b62c33 --- /dev/null +++ b/sdk/python/packages/flet/docs/services/wakelock.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Wakelock +examples: ../../examples/services/wakelock +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/accelerometerreadingevent.md b/sdk/python/packages/flet/docs/types/accelerometerreadingevent.md new file mode 100644 index 0000000000..9daec207e2 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/accelerometerreadingevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.AccelerometerReadingEvent +--- + +{{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/sharedpreferences.md b/sdk/python/packages/flet/docs/types/barometerreadingevent.md similarity index 53% rename from sdk/python/packages/flet/docs/controls/sharedpreferences.md rename to sdk/python/packages/flet/docs/types/barometerreadingevent.md index ed1f142114..ebed8768c0 100644 --- a/sdk/python/packages/flet/docs/controls/sharedpreferences.md +++ b/sdk/python/packages/flet/docs/types/barometerreadingevent.md @@ -1,5 +1,5 @@ --- -class_name: flet.SharedPreferences +class_name: flet.BarometerReadingEvent --- {{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/batterystate.md b/sdk/python/packages/flet/docs/types/batterystate.md new file mode 100644 index 0000000000..ba28c82149 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/batterystate.md @@ -0,0 +1 @@ +{{ class_all_options("flet.BatteryState", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/batterystatechangeevent.md b/sdk/python/packages/flet/docs/types/batterystatechangeevent.md new file mode 100644 index 0000000000..bdd71d012c --- /dev/null +++ b/sdk/python/packages/flet/docs/types/batterystatechangeevent.md @@ -0,0 +1 @@ +{{ class_all_options("flet.BatteryStateChangeEvent", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/browserconfiguration.md b/sdk/python/packages/flet/docs/types/browserconfiguration.md new file mode 100644 index 0000000000..ff1497e29f --- /dev/null +++ b/sdk/python/packages/flet/docs/types/browserconfiguration.md @@ -0,0 +1 @@ +{{ class_all_options("flet.BrowserConfiguration") }} diff --git a/sdk/python/packages/flet/docs/types/browsercontextmenu.md b/sdk/python/packages/flet/docs/types/browsercontextmenu.md deleted file mode 100644 index 7f1fd5b629..0000000000 --- a/sdk/python/packages/flet/docs/types/browsercontextmenu.md +++ /dev/null @@ -1 +0,0 @@ -{{ class_all_options("flet.BrowserContextMenu") }} diff --git a/sdk/python/packages/flet/docs/types/connectivitychangeevent.md b/sdk/python/packages/flet/docs/types/connectivitychangeevent.md new file mode 100644 index 0000000000..8441098f4d --- /dev/null +++ b/sdk/python/packages/flet/docs/types/connectivitychangeevent.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ConnectivityChangeEvent", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/connectivitytype.md b/sdk/python/packages/flet/docs/types/connectivitytype.md new file mode 100644 index 0000000000..2955ba743a --- /dev/null +++ b/sdk/python/packages/flet/docs/types/connectivitytype.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ConnectivityType", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/controls/storagepaths.md b/sdk/python/packages/flet/docs/types/gyroscopereadingevent.md similarity index 53% rename from sdk/python/packages/flet/docs/controls/storagepaths.md rename to sdk/python/packages/flet/docs/types/gyroscopereadingevent.md index ad8c43ef9c..f6b563fffb 100644 --- a/sdk/python/packages/flet/docs/controls/storagepaths.md +++ b/sdk/python/packages/flet/docs/types/gyroscopereadingevent.md @@ -1,5 +1,5 @@ --- -class_name: flet.StoragePaths +class_name: flet.GyroscopeReadingEvent --- {{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/index.md b/sdk/python/packages/flet/docs/types/index.md new file mode 100644 index 0000000000..fe403bf9a5 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/index.md @@ -0,0 +1,12 @@ +# Types + +Explore the shared building blocks that power Flet apps: aliases, base controls, reusable classes, enums, events, and exceptions. + +- [Aliases](aliases.md) - shorthand names and conveniences for common patterns. +- [Base Controls](../controls/basecontrol.md) - foundational control types to extend and compose. +- [Classes](alignment.md) - core data structures (layout, animation, theming, platform info, and more). +- [Decorators](component.md) - helpers for defining components, controls, and observables. +- [Enums](animationcurve.md) - constant sets that define options for controls and services. +- [Events](accelerometerreadingevent.md) - callback payloads emitted by controls and services. +- [Exceptions](fletexception/index.md) - Flet-specific error types for diagnostics and handling. +- [Methods](create_context.md) - utilities for lifecycle, hooks, and component state. diff --git a/sdk/python/packages/flet/docs/types/launchmode.md b/sdk/python/packages/flet/docs/types/launchmode.md new file mode 100644 index 0000000000..8730fb5087 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/launchmode.md @@ -0,0 +1 @@ +{{ class_all_options("flet.LaunchMode", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/controls/urllauncher.md b/sdk/python/packages/flet/docs/types/magnetometerreadingevent.md similarity index 51% rename from sdk/python/packages/flet/docs/controls/urllauncher.md rename to sdk/python/packages/flet/docs/types/magnetometerreadingevent.md index 567293b89c..765426b66b 100644 --- a/sdk/python/packages/flet/docs/controls/urllauncher.md +++ b/sdk/python/packages/flet/docs/types/magnetometerreadingevent.md @@ -1,5 +1,5 @@ --- -class_name: flet.UrlLauncher +class_name: flet.MagnetometerReadingEvent --- {{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/screenbrightnesschangeevent.md b/sdk/python/packages/flet/docs/types/screenbrightnesschangeevent.md new file mode 100644 index 0000000000..135fb6e978 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/screenbrightnesschangeevent.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ScreenBrightnessChangeEvent") }} diff --git a/sdk/python/packages/flet/docs/controls/clipboard.md b/sdk/python/packages/flet/docs/types/sensorerrorevent.md similarity index 56% rename from sdk/python/packages/flet/docs/controls/clipboard.md rename to sdk/python/packages/flet/docs/types/sensorerrorevent.md index d25a9cfa53..0e483bd3a2 100644 --- a/sdk/python/packages/flet/docs/controls/clipboard.md +++ b/sdk/python/packages/flet/docs/types/sensorerrorevent.md @@ -1,5 +1,5 @@ --- -class_name: flet.Clipboard +class_name: flet.SensorErrorEvent --- {{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/sharecupertinoactivitytype.md b/sdk/python/packages/flet/docs/types/sharecupertinoactivitytype.md new file mode 100644 index 0000000000..2919db10c0 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/sharecupertinoactivitytype.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ShareCupertinoActivityType", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/sharefile.md b/sdk/python/packages/flet/docs/types/sharefile.md new file mode 100644 index 0000000000..d396822daf --- /dev/null +++ b/sdk/python/packages/flet/docs/types/sharefile.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ShareFile", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/shareresult.md b/sdk/python/packages/flet/docs/types/shareresult.md new file mode 100644 index 0000000000..fb1a45c01e --- /dev/null +++ b/sdk/python/packages/flet/docs/types/shareresult.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ShareResult", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/shareresultstatus.md b/sdk/python/packages/flet/docs/types/shareresultstatus.md new file mode 100644 index 0000000000..424b38a9f1 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/shareresultstatus.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ShareResultStatus", separate_signature=False) }} diff --git a/sdk/python/packages/flet/docs/types/useraccelerometerreadingevent.md b/sdk/python/packages/flet/docs/types/useraccelerometerreadingevent.md new file mode 100644 index 0000000000..9e8c946ff5 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/useraccelerometerreadingevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.UserAccelerometerReadingEvent +--- + +{{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/webviewconfiguration.md b/sdk/python/packages/flet/docs/types/webviewconfiguration.md new file mode 100644 index 0000000000..7b89ae92c8 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/webviewconfiguration.md @@ -0,0 +1 @@ +{{ class_all_options("flet.WebViewConfiguration") }} diff --git a/sdk/python/packages/flet/integration_tests/examples/material/test_outlined_button.py b/sdk/python/packages/flet/integration_tests/examples/material/test_outlined_button.py index 14a94d6054..aeea041f94 100644 --- a/sdk/python/packages/flet/integration_tests/examples/material/test_outlined_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/material/test_outlined_button.py @@ -2,12 +2,11 @@ import flet as ft import flet.testing as ftt - from examples.controls.outlined_button import ( basic, custom_content, - icons, handling_clicks, + icons, ) @@ -77,19 +76,25 @@ async def test_handling_clicks(flet_app_function: ftt.FletTestApp): await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( "handling_clicks1", - await flet_app_function.page.take_screenshot(), + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), ) await flet_app_function.tester.tap(ob) await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( "handling_clicks2", - await flet_app_function.page.take_screenshot(), + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), ) await flet_app_function.tester.tap(ob) await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( "handling_clicks3", - await flet_app_function.page.take_screenshot(), + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), ) flet_app_function.create_gif( diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 2a84849f4e..4387ed6b6e 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -256,6 +256,7 @@ nav: - Creating an Extension: extend/user-extensions.md - Built-in Extensions: extend/built-in-extensions.md - API Reference: + - Overview: api-reference/index.md - Controls: - Overview: controls/index.md - Ads: @@ -264,8 +265,6 @@ nav: - BaseAd: ads/basead.md - InterstitialAd: ads/interstitialad.md # - NativeAd: ads/nativead.md - - Audio: audio/index.md - - AudioRecorder: audio_recorder/index.md - AlertDialog: controls/alertdialog.md - AnimatedSwitcher: controls/animatedswitcher.md - AppBar: controls/appbar.md @@ -356,18 +355,14 @@ nav: - ExpansionPanel: controls/expansionpanel.md - ExpansionPanelList: controls/expansionpanellist.md - ExpansionTile: controls/expansiontile.md - - FilePicker: controls/filepicker.md - FilledButton: controls/filledbutton.md - FilledIconButton: controls/fillediconbutton.md - FilledTonalButton: controls/filledtonalbutton.md - FilledTonalIconButton: controls/filledtonaliconbutton.md - - Flashlight: flashlight/index.md - FletApp: controls/fletapp.md - FloatingActionButton: controls/floatingactionbutton.md - GestureDetector: controls/gesturedetector.md - - Geolocator: geolocator/index.md - GridView: controls/gridview.md - - HapticFeedback: controls/hapticfeedback.md - Icon: controls/icon.md - IconButton: controls/iconbutton.md - Image: controls/image.md @@ -417,7 +412,6 @@ nav: - Pagelet: controls/pagelet.md - Placeholder: controls/placeholder.md - PopupMenuButton: controls/popupmenubutton.md - - PermissionHandler: permission_handler/index.md - ProgressBar: controls/progressbar.md - ProgressRing: controls/progressring.md - Radio: controls/radio.md @@ -435,11 +429,9 @@ nav: - Segment: controls/segment.md - SelectionArea: controls/selectionarea.md - Semantics: controls/semantics.md - - SemanticsService: controls/semanticsservice.md + - Screenshot: controls/screenshot.md - ShaderMask: controls/shadermask.md - Shimmer: controls/shimmer.md - - ShakeDetector: controls/shakedetector.md - - Screenshot: controls/screenshot.md - Slider: controls/slider.md - SnackBar: controls/snackbar.md - Stack: controls/stack.md @@ -460,6 +452,32 @@ nav: - View: controls/view.md - WebView: webview/index.md - WindowDragArea: controls/windowdragarea.md + - Services: + - Overview: services/index.md + - Accelerometer: services/accelerometer.md + - Audio: audio/index.md + - AudioRecorder: audio_recorder/index.md + - Barometer: services/barometer.md + - Battery: services/battery.md + - BrowserContextMenu: services/browsercontextmenu.md + - Clipboard: services/clipboard.md + - Connectivity: services/connectivity.md + - FilePicker: services/filepicker.md + - Flashlight: flashlight/index.md + - Geolocator: geolocator/index.md + - Gyroscope: services/gyroscope.md + - HapticFeedback: services/hapticfeedback.md + - Magnetometer: services/magnetometer.md + - PermissionHandler: permission_handler/index.md + - ScreenBrightness: services/screenbrightness.md + - SemanticsService: services/semanticsservice.md + - ShakeDetector: services/shakedetector.md + - Share: services/share.md + - SharedPreferences: services/sharedpreferences.md + - StoragePaths: services/storagepaths.md + - UrlLauncher: services/urllauncher.md + - UserAccelerometer: services/useraccelerometer.md + - Wakelock: services/wakelock.md - CLI: - Overview: cli/index.md - flet build: cli/flet-build.md @@ -472,7 +490,8 @@ nav: - flet publish: cli/flet-publish.md - flet run: cli/flet-run.md - flet serve: cli/flet-serve.md - - API: + - Types: + - Overview: types/index.md - Aliases: types/aliases.md - Base Controls: - AdaptiveControl: controls/adaptivecontrol.md @@ -516,7 +535,6 @@ nav: - BoxConstraints: types/boxconstraints.md - BoxDecoration: types/boxdecoration.md - BoxShadow: types/boxshadow.md - - BrowserContextMenu: types/browsercontextmenu.md - ButtonStyle: types/buttonstyle.md - Charts: - BarChartEvent: charts/types/bar_chart_event.md @@ -558,7 +576,6 @@ nav: - ScatterChartSpot: charts/types/scatter_chart_spot.md - ScatterChartSpotTooltip: charts/types/scatter_chart_spot_tooltip.md - ScatterChartTooltip: charts/types/scatter_chart_tooltip.md - - Clipboard: controls/clipboard.md - ColorFilter: types/colorfilter.md - Context: types/context.md - ControlState: types/controlstate.md @@ -671,9 +688,7 @@ nav: - Rotate: types/rotate.md - Scale: types/scale.md - ShapeBorder: types/shapeborder.md - - SharedPreferences: controls/sharedpreferences.md - Size: types/size.md - - StoragePaths: controls/storagepaths.md - StrutStyle: types/strutstyle.md - TemplateRoute: types/templateroute.md - Tester: types/tester.md @@ -727,9 +742,10 @@ nav: - TextTheme: types/texttheme.md - TimePickerTheme: types/timepickertheme.md - TooltipTheme: types/tooltiptheme.md + - BrowserConfiguration: types/browserconfiguration.md - Tooltip: types/tooltip.md - Url: types/url.md - - UrlLauncher: controls/urllauncher.md + - WebViewConfiguration: types/webviewconfiguration.md - UnderlineTabIndicator: types/underlinetabindicator.md - Video: - PlaylistMode: video/types/playlist_mode.md @@ -831,6 +847,7 @@ nav: - TimePickerEntryMode: types/timepickerentrymode.md - TimePickerHourFormat: types/timepickerhourformat.md - TooltipTriggerMode: types/tooltiptriggermode.md + - LaunchMode: types/launchmode.md - UrlTarget: types/urltarget.md - VerticalAlignment: types/verticalalignment.md - VisualDensity: types/visualdensity.md @@ -839,10 +856,16 @@ nav: - WindowEventType: types/windoweventtype.md - WindowResizeEdge: types/windowresizeedge.md - Events: + - AccelerometerReadingEvent: types/accelerometerreadingevent.md - Ads: - PaidAdRequest: ads/types/paidadevent.md - AppLifecycleStateChangeEvent: types/applifecyclestatechangeevent.md - AutoCompleteSelectEvent: types/autocompleteselectevent.md + - BarometerReadingEvent: types/barometerreadingevent.md + - BatteryState: types/batterystate.md + - BatteryStateChangeEvent: types/batterystatechangeevent.md + - ConnectivityType: types/connectivitytype.md + - ConnectivityChangeEvent: types/connectivitychangeevent.md - CanvasResizeEvent: types/canvasresizeevent.md - ContextMenuDismissEvent: types/contextmenudismissevent.md - ContextMenuSelectEvent: types/contextmenuselectevent.md @@ -858,6 +881,7 @@ nav: - DragWillAcceptEvent: types/dragwillacceptevent.md - Event: types/event.md - FilePickerUploadEvent: types/filepickeruploadevent.md + - GyroscopeReadingEvent: types/gyroscopereadingevent.md - HoverEvent: types/hoverevent.md - KeyboardEvent: types/keyboardevent.md - KeyDownEvent: types/keydownevent.md @@ -866,6 +890,7 @@ nav: - LoginEvent: types/loginevent.md - LongPressEndEvent: types/longpressendevent.md - LongPressStartEvent: types/longpressstartevent.md + - MagnetometerReadingEvent: types/magnetometerreadingevent.md - MultiTapEvent: types/multitapevent.md - MultiViewAddEvent: types/multiviewaddevent.md - MultiViewRemoveEvent: types/multiviewremoveevent.md @@ -876,14 +901,21 @@ nav: - PlatformBrightnessChangeEvent: types/platformbrightnesschangeevent.md - PointerEvent: types/pointerevent.md - RouteChangeEvent: types/routechangeevent.md + - SensorErrorEvent: types/sensorerrorevent.md - ScaleEndEvent: types/scaleendevent.md - ScaleStartEvent: types/scalestartevent.md - ScaleUpdateEvent: types/scaleupdateevent.md + - ScreenBrightnessChangeEvent: types/screenbrightnesschangeevent.md + - ShareResult: types/shareresult.md + - ShareResultStatus: types/shareresultstatus.md + - ShareFile: types/sharefile.md + - ShareCupertinoActivityType: types/sharecupertinoactivitytype.md - ScrollEvent: types/scrollevent.md - TabBarHoverEvent: types/tabbarhoverevent.md - TapEvent: types/tapevent.md - TextSelectionChangeEvent: types/textselectionchangeevent.md - TimePickerEntryModeChangeEvent: types/timepickerentrymodechangeevent.md + - UserAccelerometerReadingEvent: types/useraccelerometerreadingevent.md - ViewPopEvent: types/viewpopevent.md - WindowEvent: types/windowevent.md - Exceptions: diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index e86c4e66f0..ae213adddc 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -414,8 +414,23 @@ ScrollDirection, ScrollType, ) +from flet.controls.services.accelerometer import ( + Accelerometer, + AccelerometerReadingEvent, +) +from flet.controls.services.barometer import Barometer, BarometerReadingEvent +from flet.controls.services.battery import ( + Battery, + BatteryState, + BatteryStateChangeEvent, +) from flet.controls.services.browser_context_menu import BrowserContextMenu from flet.controls.services.clipboard import Clipboard +from flet.controls.services.connectivity import ( + Connectivity, + ConnectivityChangeEvent, + ConnectivityType, +) from flet.controls.services.file_picker import ( FilePicker, FilePickerFile, @@ -423,13 +438,37 @@ FilePickerUploadEvent, FilePickerUploadFile, ) +from flet.controls.services.gyroscope import Gyroscope, GyroscopeReadingEvent from flet.controls.services.haptic_feedback import HapticFeedback +from flet.controls.services.magnetometer import Magnetometer, MagnetometerReadingEvent +from flet.controls.services.screen_brightness import ( + ScreenBrightness, + ScreenBrightnessChangeEvent, +) from flet.controls.services.semantics_service import Assertiveness, SemanticsService +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service from flet.controls.services.shake_detector import ShakeDetector +from flet.controls.services.share import ( + Share, + ShareCupertinoActivityType, + ShareFile, + ShareResult, + ShareResultStatus, +) from flet.controls.services.shared_preferences import SharedPreferences from flet.controls.services.storage_paths import StoragePaths -from flet.controls.services.url_launcher import UrlLauncher +from flet.controls.services.url_launcher import ( + BrowserConfiguration, + LaunchMode, + UrlLauncher, + WebViewConfiguration, +) +from flet.controls.services.user_accelerometer import ( + UserAccelerometer, + UserAccelerometerReadingEvent, +) +from flet.controls.services.wakelock import Wakelock from flet.controls.template_route import TemplateRoute from flet.controls.text_style import ( StrutStyle, @@ -539,6 +578,8 @@ from flet.pubsub.pubsub_hub import PubSubHub __all__ = [ + "Accelerometer", + "AccelerometerReadingEvent", "AdaptiveControl", "AlertDialog", "Alignment", @@ -569,8 +610,13 @@ "BadgeValue", "Banner", "BannerTheme", + "Barometer", + "BarometerReadingEvent", "BaseControl", "BasePage", + "Battery", + "BatteryState", + "BatteryStateChangeEvent", "BeveledRectangleBorder", "BlendMode", "Blur", @@ -595,6 +641,7 @@ "BoxShadowValue", "BoxShape", "Brightness", + "BrowserConfiguration", "BrowserContextMenu", "Button", "ButtonStyle", @@ -617,6 +664,9 @@ "Colors", "Column", "Component", + "Connectivity", + "ConnectivityChangeEvent", + "ConnectivityType", "ConstrainedControl", "Container", "Context", @@ -734,6 +784,8 @@ "Gradient", "GradientTileMode", "GridView", + "Gyroscope", + "GyroscopeReadingEvent", "HapticFeedback", "HoverEvent", "Icon", @@ -760,6 +812,7 @@ "KeyboardListener", "KeyboardType", "LabelPosition", + "LaunchMode", "LayoutControl", "LinearGradient", "LinuxDeviceInfo", @@ -776,6 +829,8 @@ "LongPressMoveUpdateEvent", "LongPressStartEvent", "MacOsDeviceInfo", + "Magnetometer", + "MagnetometerReadingEvent", "MainAxisAlignment", "Margin", "MarginValue", @@ -871,6 +926,8 @@ "ScaleStartEvent", "ScaleUpdateEvent", "ScaleValue", + "ScreenBrightness", + "ScreenBrightnessChangeEvent", "Screenshot", "ScrollDirection", "ScrollEvent", @@ -888,10 +945,16 @@ "SelectionArea", "Semantics", "SemanticsService", + "SensorErrorEvent", "Service", "ShaderMask", "ShakeDetector", "ShapeBorder", + "Share", + "ShareCupertinoActivityType", + "ShareFile", + "ShareResult", + "ShareResultStatus", "SharedPreferences", "Shimmer", "ShimmerDirection", @@ -965,15 +1028,19 @@ "Url", "UrlLauncher", "UrlTarget", + "UserAccelerometer", + "UserAccelerometerReadingEvent", "ValueKey", "VerticalAlignment", "VerticalDivider", "View", "ViewPopEvent", "VisualDensity", + "Wakelock", "WebBrowserName", "WebDeviceInfo", "WebRenderer", + "WebViewConfiguration", "Window", "WindowDragArea", "WindowEvent", diff --git a/sdk/python/packages/flet/src/flet/controls/base_page.py b/sdk/python/packages/flet/src/flet/controls/base_page.py index 0a2a888eb2..60b937df7e 100644 --- a/sdk/python/packages/flet/src/flet/controls/base_page.py +++ b/sdk/python/packages/flet/src/flet/controls/base_page.py @@ -253,7 +253,6 @@ def handle_page_size(e): [`Page.window`][flet.Page.window] instead. """ - services: list[Service] = field(default_factory=list, metadata={"skip": True}) _overlay: "Overlay" = field(default_factory=lambda: Overlay()) _dialogs: "Dialogs" = field(default_factory=lambda: Dialogs()) @@ -620,6 +619,15 @@ def auto_scroll(self) -> bool: def auto_scroll(self, value: bool): self.__root_view().auto_scroll = value + # services + @property + def services(self) -> list[Service]: + return self.__root_view().services + + @services.setter + def services(self, value: list[Service]): + self.__root_view().services = value + # Magic methods def __contains__(self, item: Control) -> bool: return item in self.controls diff --git a/sdk/python/packages/flet/src/flet/controls/core/view.py b/sdk/python/packages/flet/src/flet/controls/core/view.py index f31447d890..23bd290133 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/view.py +++ b/sdk/python/packages/flet/src/flet/controls/core/view.py @@ -15,6 +15,7 @@ from flet.controls.material.navigation_drawer import NavigationDrawer from flet.controls.padding import Padding, PaddingValue from flet.controls.scrollable_control import ScrollableControl +from flet.controls.services.service import Service from flet.controls.transform import OffsetValue from flet.controls.types import ( ColorValue, @@ -160,8 +161,22 @@ class View(ScrollableControl, LayoutControl): If `True`, the view is a fullscreen modal dialog. """ + services: list[Service] = field(default_factory=list, metadata={"skip": True}) + """ + A list of [`Service`][flet.] controls associated with this view. + """ + can_pop: bool = True + """ + Whether the view can be popped. + """ + on_confirm_pop: Optional[ControlEventHandler["View"]] = None + """ + An event handler that is called when the view is about to be popped. + You can use this event to confirm or cancel the pop action by calling + [`confirm_pop`][(c).] method. + """ def init(self): super().init() diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py index fb71001b9d..f8d5a38dd3 100644 --- a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py @@ -155,6 +155,6 @@ class CupertinoSwitch(LayoutControl): on_image_error: Optional[ControlEventHandler["CupertinoSwitch"]] = None """ - Called when [`active_thumb_image`][(c).] or - [`inactive_thumb_image`][(c).] fails to load. + Called when [`active_thumb_image_src`][(c).] or + [`inactive_thumb_image_src`][(c).] fails to load. """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py b/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py index 1e51f1bcd8..043837c9d3 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py +++ b/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py @@ -237,7 +237,7 @@ class SnackBar(DialogControl): If `False`, the snack bar will be dismissed after the timeout. - If not provided, but the snackbar ['action'][c.] is not null, + If not provided, but the snackbar [`action`][(c).] is not null, the snackbar will persist as well. """ diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 2958ac5a70..573b6b66fa 100644 --- a/sdk/python/packages/flet/src/flet/controls/page.py +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -193,45 +193,6 @@ class Page(BasePage): The list of multi-views associated with this page. """ - browser_context_menu: BrowserContextMenu = field( - default_factory=lambda: BrowserContextMenu(), metadata={"skip": True} - ) - """ - Used to enable or disable the context menu that appears when the user - right-clicks on the web page. - - Limitation: - Web only. - """ - - shared_preferences: SharedPreferences = field( - default_factory=lambda: SharedPreferences(), metadata={"skip": True} - ) - """ - Provides a persistent key-value storage for simple data types. - """ - - clipboard: Clipboard = field( - default_factory=lambda: Clipboard(), metadata={"skip": True} - ) - """ - Provides access to the system clipboard. - """ - - storage_paths: StoragePaths = field( - default_factory=lambda: StoragePaths(), metadata={"skip": True} - ) - """ - Provides the information about common storage paths. - """ - - url_launcher: UrlLauncher = field( - default_factory=lambda: UrlLauncher(), metadata={"skip": True} - ) - """ - Provides methods for launching URLs. - """ - window: Window = field(default_factory=lambda: Window()) """ Provides properties/methods/events to monitor and control the @@ -424,8 +385,7 @@ class Page(BasePage): """ TBD """ - _services: list[Service] = field(default_factory=list) - _user_services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry()) + _services: ServiceRegistry = field(default_factory=ServiceRegistry) def __post_init__( self, @@ -435,15 +395,6 @@ def __post_init__( BasePage.__post_init__(self, ref) self._i = 1 self.__session = weakref.ref(sess) - - # page services - self._services = [ - self.browser_context_menu, - self.shared_preferences, - self.clipboard, - self.url_launcher, - self.storage_paths, - ] self.__last_route = None self.__query: QueryString = QueryString(self) self.__authorization: Optional[Authorization] = None @@ -583,7 +534,12 @@ def run_thread( partial(handler_with_context, *args, **kwargs), ) - @deprecated("Use push_route() instead.", version="0.70.0", show_parentheses=True) + @deprecated( + "Use push_route() instead.", + version="0.70.0", + delete_version="0.90.0", + show_parentheses=True, + ) def go( self, route: str, skip_route_change_event: bool = False, **kwargs: Any ) -> None: @@ -731,9 +687,12 @@ async def login( if on_open_authorization_url: await on_open_authorization_url(authorization_url) else: - await self.launch_url( - authorization_url, "flet_oauth_signin", web_popup_window=self.web - ) + if self.web: + await UrlLauncher().open_window( + authorization_url, title="flet_oauth_signin" + ) + else: + await UrlLauncher().launch_url(authorization_url) else: await self.__authorization.dehydrate_token(saved_token) @@ -793,6 +752,11 @@ def logout(self) -> None: elif callable(self.on_logout): self.on_logout(e) + @deprecated( + "Use UrlLauncher().launch_url() instead.", + version="0.90.0", + show_parentheses=True, + ) async def launch_url( self, url: Union[str, Url], @@ -816,14 +780,21 @@ async def launch_url( web_popup_window_width: Popup window width. web_popup_window_height: Popup window height. """ - await self.url_launcher.launch_url( - url, - web_popup_window_name=web_popup_window_name, - web_popup_window=web_popup_window, - web_popup_window_width=web_popup_window_width, - web_popup_window_height=web_popup_window_height, - ) + if web_popup_window: + await UrlLauncher().open_window( + url, + title=web_popup_window_name, + width=web_popup_window_width, + height=web_popup_window_height, + ) + else: + await UrlLauncher().launch_url(url) + @deprecated( + "Use UrlLauncher().can_launch_url() instead.", + version="0.90.0", + show_parentheses=True, + ) async def can_launch_url(self, url: str) -> bool: """ Checks whether the specified URL can be handled by some app @@ -844,15 +815,20 @@ async def can_launch_url(self, url: str) -> bool: schemes that are always assumed to be supported (such as http(s)), as web pages are never allowed to query installed applications. """ - return await self.url_launcher.can_launch_url(url) + return await UrlLauncher().can_launch_url(url) + @deprecated( + "Use UrlLauncher().close_in_app_web_view() instead.", + version="0.90.0", + show_parentheses=True, + ) async def close_in_app_web_view(self) -> None: """ Closes in-app web view opened with `launch_url()`. 📱 Mobile only. """ - await self.url_launcher.close_in_app_web_view() + await UrlLauncher().close_in_app_web_view() @property def session(self) -> "Session": @@ -912,6 +888,56 @@ def pubsub(self) -> "PubSubClient": """ return self.session.pubsub_client + @property + @deprecated("Use UrlLauncher() instead.", version="0.70.0", delete_version="0.90.0") + def url_launcher(self) -> UrlLauncher: + """ + DEPRECATED: The UrlLauncher service for the current page. + """ + return UrlLauncher() + + @property + @deprecated( + "Use BrowserContextMenu() instead.", version="0.70.0", delete_version="0.90.0" + ) + def browser_context_menu(self): + """ + DEPRECATED: The BrowserContextMenu service for the current page. + """ + + return BrowserContextMenu() + + @property + @deprecated( + "Use SharedPreferences() instead.", version="0.70.0", delete_version="0.90.0" + ) + def shared_preferences(self): + """ + DEPRECATED: The SharedPreferences service for the current page. + """ + + return SharedPreferences() + + @property + @deprecated("Use Clipboard() instead.", version="0.70.0", delete_version="0.90.0") + def clipboard(self): + """ + DEPRECATED: The Clipboard service for the current page. + """ + + return Clipboard() + + @property + @deprecated( + "Use StoragePaths() instead.", version="0.70.0", delete_version="0.90.0" + ) + def storage_paths(self): + """ + DEPRECATED: The StoragePaths service for the current page. + """ + + return StoragePaths() + async def get_device_info(self) -> Optional[DeviceInfo]: """ Returns device information. diff --git a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py new file mode 100644 index 0000000000..68825c6750 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -0,0 +1,99 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_error_event import SensorErrorEvent +from flet.controls.services.service import Service +from flet.utils.platform_utils import FletUnsupportedPlatformException + +__all__ = ["Accelerometer", "AccelerometerReadingEvent"] + + +@dataclass(kw_only=True) +class AccelerometerReadingEvent(Event["Accelerometer"]): + """ + Discrete reading from an accelerometer. Accelerometers measure the velocity + of the device. Note that these readings include the effects of gravity. + Put simply, you can use accelerometer readings to tell if the device + is moving in a particular direction. + """ + + x: float + """Acceleration along the X axis, in `m/s^2`.""" + + y: float + """Acceleration along the Y axis, in `m/s^2`.""" + + z: float + """Acceleration along the Z axis, in `m/s^2`.""" + + timestamp: datetime + """Event timestamp.""" + + +@control("Accelerometer") +class Accelerometer(Service): + """ + Streams raw accelerometer [readings][flet.AccelerometerReadingEvent], + which describe the acceleration of the device, in `m/s^2`, including + the effects of gravity. + + Unlike [UserAccelerometer][flet.], + this service reports raw data from the accelerometer (physical sensor + embedded in the mobile device) without any post-processing. + + The accelerometer is unable to distinguish between the effect of an + accelerated movement of the device and the effect of the surrounding + gravitational field. This means that, at the surface of Earth, + even if the device is completely still, the reading of [`Accelerometer`][flet.] + is an acceleration of intensity 9.8 directed upwards (the opposite of + the graviational acceleration). This can be used to infer information + about the position of the device (horizontal/vertical/tilted). + Accelerometer reports zero acceleration if the device is free falling. + + Note: + * Supported platforms: Android, iOS and web. + * Web ignores requested sampling intervals. + """ + + enabled: bool = True + """ + Whether the sensor should be sampled. Disable to stop streaming. + """ + + interval: Optional[Duration] = None + """ + Desired sampling interval provided as a [`Duration`][flet.Duration]. + Defaults to 200 ms. + + Note that mobile platforms treat this value as a suggestion and the actual + rate can differ depending on hardware and OS limitations. + """ + + cancel_on_error: bool = True + """ + Whether the stream subscription should cancel on the first sensor error. + """ + + on_reading: Optional[EventHandler[AccelerometerReadingEvent]] = None + """ + Fires when a new reading is available. + + `event` exposes `x`, `y`, `z` acceleration values and `timestamp` + (microseconds since epoch). + """ + + on_error: Optional[EventHandler[SensorErrorEvent]] = None + """ + Fired when the platform reports a sensor error (for example when the device + does not expose the accelerometer). `event.message` contains the error text. + """ + + def before_update(self): + if not (self.page.web or self.page.platform.is_mobile()): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android, iOS and web." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/barometer.py b/sdk/python/packages/flet/src/flet/controls/services/barometer.py new file mode 100644 index 0000000000..8773b21f19 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/barometer.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.duration import Duration +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.sensor_error_event import SensorErrorEvent +from flet.controls.services.service import Service + +__all__ = ["Barometer", "BarometerReadingEvent"] + + +@dataclass(kw_only=True) +class BarometerReadingEvent(Event["Barometer"]): + """ + A sensor sample from a barometer. + + Barometers measure the atmospheric pressure surrounding the sensor, + returning values in hectopascals `hPa`. + + Consider that these samples may be affected by altitude and weather conditions, + and can be used to predict short-term weather changes or determine altitude. + + Note that water-resistant phones or similar sealed devices may experience + pressure fluctuations as the device is held or used, due to changes + in pressure caused by handling the device. + + An altimeter is an example of a general utility for barometer data. + """ + + pressure: float + """Atmospheric pressure reading, in hectopascals (`hPa`).""" + + timestamp: datetime + """Event timestamp.""" + + +@control("Barometer") +class Barometer(Service): + """ + Streams barometer [readings][flet.BarometerReadingEvent] + (atmospheric pressure in `hPa`). Useful for altitude calculations + and weather-related experiences. + + Note: + * Supported platforms: Android, iOS. + * Barometer APIs are not exposed on the web or desktop platforms. + * iOS ignores custom sampling intervals. + + /// admonition | Running on iOS + type: danger + On iOS you must also include a key called `NSMotionUsageDescription` + in your app's `Info.plist` file. This key provides a message that tells + the user why the app is requesting access to the device's motion data. + Barometer service needs access to motion data to get barometer data. + + For example, add the following to your `pyproject.toml` file: + ```toml + [tool.flet.ios.info] + NSMotionUsageDescription = "This app requires access to the barometer to provide altitude information." + ``` + + **Adding `NSMotionUsageDescription` is a requirement and not doing so will + crash your app when it attempts to access motion data.** + /// + """ # noqa: E501 + + enabled: bool = True + """ + Whether the sensor should be sampled. Disable to stop streaming. + """ + + interval: Optional[Duration] = None + """ + Desired sampling interval provided as a [`Duration`][flet.Duration]. + Defaults to 200 ms, though + some platforms (such as iOS) ignore custom sampling intervals. + """ + + cancel_on_error: bool = True + """ + Whether the stream subscription should cancel on the first sensor error. + """ + + on_reading: Optional[EventHandler[BarometerReadingEvent]] = None + """ + Fires when a new reading is available. + + `event` contains `pressure` (hPa) and `timestamp` (microseconds + since epoch). + """ + + on_error: Optional[EventHandler[SensorErrorEvent]] = None + """ + Fired when the platform reports a sensor error. `event.message` is the error + description. + """ + + def before_update(self): + if self.page.web or not self.page.platform.is_mobile(): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android and iOS." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/battery.py b/sdk/python/packages/flet/src/flet/controls/services/battery.py new file mode 100644 index 0000000000..4ed27a17e4 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/battery.py @@ -0,0 +1,81 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.services.service import Service + +__all__ = ["Battery", "BatteryState", "BatteryStateChangeEvent"] + + +class BatteryState(Enum): + """ + Battery state. + """ + + CHARGING = "charging" + """ + The battery is currently charging. + """ + CONNECTED_NOT_CHARGING = "connectedNotCharging" + """ + The battery is connected to a power source but not charging. + """ + DISCHARGING = "discharging" + """ + The battery is discharging. + """ + FULL = "full" + """ + The battery is fully charged. + """ + UNKNOWN = "unknown" + """ + The battery state is unknown. + """ + + +@dataclass +class BatteryStateChangeEvent(Event["Battery"]): + """ + Event fired when battery state changes. + """ + + state: BatteryState + """ + Current battery state. + """ + + +@control("Battery") +class Battery(Service): + """ + Provides access to device battery information and state changes. + """ + + on_state_change: Optional[EventHandler[BatteryStateChangeEvent]] = None + """ + Called when battery state changes (charging, discharging, full, unknown). + """ + + async def get_battery_level(self) -> int: + """ + Returns current battery level as a percentage `0..100`. + """ + + return await self._invoke_method("get_battery_level") + + async def get_battery_state(self) -> BatteryState: + """ + Returns current battery state. + """ + + return BatteryState(await self._invoke_method("get_battery_state")) + + async def is_in_battery_save_mode(self) -> bool: + """ + Returns `True` if the device is currently in battery save mode. + """ + + return await self._invoke_method("is_in_battery_save_mode") diff --git a/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py b/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py index 1b95cf8677..ddd3cdc205 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py +++ b/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py @@ -6,15 +6,38 @@ @control("BrowserContextMenu") class BrowserContextMenu(Service): + """ + Controls the browser's context menu on the web platform. + + The context menu is the menu that appears on right clicking or selecting + text in the browser, for example. + + On web, by default, the browser's context menu is enabled and + Flet's context menus are hidden. + + On all non-web platforms, this does nothing. + """ + def __post_init__(self, ref): super().__post_init__(ref) self.__disabled = False async def enable(self): + """ + Enable the browser's context menu. + + By default, when the app starts, the browser's context menu is already enabled. + """ await self._invoke_method("enable_menu") self.__disabled = False async def disable(self): + """ + Disable the browser's context menu. + + By default, when the app starts, the browser's context menu is + already enabled. + """ await self._invoke_method("disable_menu") self.__disabled = True diff --git a/sdk/python/packages/flet/src/flet/controls/services/clipboard.py b/sdk/python/packages/flet/src/flet/controls/services/clipboard.py index 86d0b1d978..0af93afe2d 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/clipboard.py +++ b/sdk/python/packages/flet/src/flet/controls/services/clipboard.py @@ -8,30 +8,18 @@ @control("Clipboard") class Clipboard(Service): + """ + Provides access to the system clipboard. + """ + async def set(self, value: str) -> None: """ - Set clipboard data on a client side (user's web browser or a desktop). - - /// details | Example - type: example - - ```python - Example - TBD - ``` - /// + Stores the given clipboard data on the clipboard. """ await self._invoke_method("set", {"data": value}) async def get(self) -> Optional[str]: """ - Set clipboard data on a client side (user's web browser or a desktop). - - /// details | Example - type: example - - ```python - Example - TBD - ``` - /// + Retrieves data from the clipboard. """ return await self._invoke_method("get") diff --git a/sdk/python/packages/flet/src/flet/controls/services/connectivity.py b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py new file mode 100644 index 0000000000..8da43804e1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py @@ -0,0 +1,74 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.services.service import Service + +__all__ = ["Connectivity", "ConnectivityChangeEvent", "ConnectivityType"] + + +class ConnectivityType(Enum): + """ + Connectivity states. + """ + + BLUETOOTH = "bluetooth" + """ + Bluetooth connectivity. + """ + ETHERNET = "ethernet" + """ + Ethernet connectivity. + """ + MOBILE = "mobile" + """ + Mobile data connectivity. + """ + NONE = "none" + """ + No connectivity. + """ + OTHER = "other" + """ + Other connectivity. + """ + VPN = "vpn" + """ + VPN connectivity. + """ + WIFI = "wifi" + """ + Wi-Fi connectivity. + """ + + +@dataclass +class ConnectivityChangeEvent(Event["Connectivity"]): + """Event fired when connectivity changes.""" + + connectivity: list[ConnectivityType] + """ + Current connectivity type(s). + """ + + +@control("Connectivity") +class Connectivity(Service): + """ + Provides device connectivity status and change notifications. + """ + + on_change: Optional[EventHandler[ConnectivityChangeEvent]] = None + """ + Called when connectivity changes. + """ + + async def get_connectivity(self) -> list[ConnectivityType]: + """ + Returns the current connectivity type(s). + """ + + result = await self._invoke_method("get_connectivity") + return [ConnectivityType(r) for r in result] diff --git a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py new file mode 100644 index 0000000000..5f55217c59 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.duration import Duration +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.sensor_error_event import SensorErrorEvent +from flet.controls.services.service import Service + +__all__ = ["Gyroscope", "GyroscopeReadingEvent"] + + +@dataclass(kw_only=True) +class GyroscopeReadingEvent(Event["Gyroscope"]): + """ + Discrete reading from a gyroscope. + + Gyroscope sample containing device rotation rate (`rad/s`) around each + axis plus the microsecond timestamp. + """ + + x: float + """Rotation rate around the X axis, in `rad/s`.""" + + y: float + """Rotation rate around the Y axis, in `rad/s`.""" + + z: float + """Rotation rate around the Z axis, in `rad/s`.""" + + timestamp: datetime + """Event timestamp.""" + + +@control("Gyroscope") +class Gyroscope(Service): + """ + Streams gyroscope [readings][flet.GyroscopeReadingEvent], + reporting device rotation rate around each axis in `rad/s`. + + Note: + * Supported platforms: Android, iOS and web. + * Web ignores requested sampling intervals. + """ + + enabled: bool = True + """ + Whether the sensor should be sampled. Disable to stop streaming. + """ + + interval: Optional[Duration] = None + """ + Desired sampling interval provided as a [`Duration`][flet.Duration]. + Defaults to 200 ms. + """ + + cancel_on_error: bool = True + """ + Whether the stream subscription should cancel on the first sensor error. + """ + + on_reading: Optional[EventHandler[GyroscopeReadingEvent]] = None + """ + Fires when a new reading is available. + + `event` contains `x`, `y`, `z` rotation rates and `timestamp` + (microseconds since epoch). + """ + + on_error: Optional[EventHandler[SensorErrorEvent]] = None + """ + Fired when the platform reports a sensor error. `event.message` is the error + description. + """ + + def before_update(self): + if not (self.page.web or self.page.platform.is_mobile()): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android, iOS and web." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py new file mode 100644 index 0000000000..71ced20b8f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py @@ -0,0 +1,90 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.duration import Duration +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.sensor_error_event import SensorErrorEvent +from flet.controls.services.service import Service + +__all__ = ["Magnetometer", "MagnetometerReadingEvent"] + + +@dataclass(kw_only=True) +class MagnetometerReadingEvent(Event["Magnetometer"]): + """ + A sensor sample from a magnetometer. + + Magnetometers measure the ambient magnetic field surrounding the sensor, + returning values in microteslas `μT` for each three-dimensional axis. + + Consider that these samples may bear effects of Earth's magnetic field + as well as local factors such as the metal of the device itself + or nearby magnets, though most devices compensate for these factors. + + A compass is an example of a general utility for magnetometer data. + """ + + x: float + """Ambient magnetic field on the X axis, in microteslas (`uT`).""" + + y: float + """Ambient magnetic field on the Y axis, in `uT`.""" + + z: float + """Ambient magnetic field on the Z axis, in `uT`.""" + + timestamp: datetime + """Event timestamp.""" + + +@control("Magnetometer") +class Magnetometer(Service): + """ + Streams magnetometer [readings][flet.MagnetometerReadingEvent] + reporting the ambient magnetic field (`uT`) per axis for compass-style + use cases. + + Note: + * Supported platforms: Android, iOS. + * Magnetometer APIs are not available on web. + or desktop, so always handle `on_error` to detect unsupported hardware. + """ + + enabled: bool = True + """ + Whether the sensor should be sampled. Disable to stop streaming. + """ + + interval: Optional[Duration] = None + """ + Desired sampling interval provided as a [`Duration`][flet.Duration]. + Defaults to 200 ms. + """ + + cancel_on_error: bool = True + """ + Whether the stream subscription should cancel on the first sensor error. + """ + + on_reading: Optional[EventHandler[MagnetometerReadingEvent]] = None + """ + Fires when a new reading is available. + + `event` contains `x`, `y`, `z` magnetic field strengths (uT) + and `timestamp` (microseconds since epoch). + """ + + on_error: Optional[EventHandler[SensorErrorEvent]] = None + """ + Fired when the platform reports a sensor error. `event.message` is the error + description. + """ + + def before_update(self): + if self.page.web or not self.page.platform.is_mobile(): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android and iOS." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py new file mode 100644 index 0000000000..c73d442934 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py @@ -0,0 +1,134 @@ +from dataclasses import dataclass +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.service import Service +from flet.controls.types import Number + +__all__ = ["ScreenBrightness", "ScreenBrightnessChangeEvent"] + + +@dataclass +class ScreenBrightnessChangeEvent(Event["ScreenBrightness"]): + """ + Event fired when screen brightness changes. + """ + + brightness: float + """ + The new screen brightness, in range `0.0..1.0`. + """ + + +@control("ScreenBrightness") +class ScreenBrightness(Service): + """ + Provides access to control and observe system and application screen brightness. + + Note: + * Supported platforms: Android, iOS. + + /// admonition | Running on Android + type: warning + To adjust the system brightness on Android, add the following permission + in your `pyproject.toml` file: + ```toml + [tool.flet.android.permission] + "android.permission.WRITE_SETTINGS" = true + ``` + /// + """ + + on_system_screen_brightness_change: Optional[ + EventHandler[ScreenBrightnessChangeEvent] + ] = None + """ + Called when the **system** screen brightness changes. + """ + + on_application_screen_brightness_change: Optional[ + EventHandler[ScreenBrightnessChangeEvent] + ] = None + """ + Called when the **application** screen brightness changes. + """ + + async def get_system_screen_brightness(self) -> float: + """ + Returns the current system screen brightness, in range `0.0..1.0`. + """ + + return await self._invoke_method("get_system_screen_brightness") + + async def can_change_system_screen_brightness(self) -> bool: + """ + Returns whether the app is allowed to change the system screen brightness. + """ + + return await self._invoke_method("can_change_system_screen_brightness") + + async def set_system_screen_brightness(self, brightness: Number): + """ + Sets the system screen brightness. + """ + + await self._invoke_method("set_system_screen_brightness", {"value": brightness}) + + async def get_application_screen_brightness(self) -> float: + """ + Returns the current application screen brightness, in range `0.0..1.0`. + """ + + return await self._invoke_method("get_application_screen_brightness") + + async def set_application_screen_brightness(self, brightness: Number): + """ + Sets the application screen brightness. + """ + + await self._invoke_method( + "set_application_screen_brightness", {"value": brightness} + ) + + async def reset_application_screen_brightness(self): + """ + Resets the application screen brightness back to the system value. + """ + + await self._invoke_method("reset_application_screen_brightness") + + async def is_animate(self) -> bool: + """ + Returns `True` if brightness changes are animated (platform dependent). + """ + + return await self._invoke_method("is_animate") + + async def set_animate(self, animate: bool): + """ + Enables or disables animation for brightness changes. + """ + + await self._invoke_method("set_animate", {"value": animate}) + + async def is_auto_reset(self) -> bool: + """ + Returns `True` if brightness resets automatically on lifecycle changes. + """ + + return await self._invoke_method("is_auto_reset") + + async def set_auto_reset(self, auto_reset: bool): + """ + Enables or disables automatic reset to system brightness on lifecycle changes. + """ + + await self._invoke_method("set_auto_reset", {"value": auto_reset}) + + def before_update(self): + if self.page.web or not self.page.platform.is_mobile(): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android and iOS." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/sensor_error_event.py b/sdk/python/packages/flet/src/flet/controls/services/sensor_error_event.py new file mode 100644 index 0000000000..d69c56b636 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/sensor_error_event.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +from flet.controls.control_event import Event, EventControlType + +__all__ = ["SensorErrorEvent"] + + +@dataclass(kw_only=True) +class SensorErrorEvent(Event[EventControlType]): + """ + Generic sensor error event. `message` contains the platform error text. + """ + + message: str + """Human-readable description of the sensor error.""" diff --git a/sdk/python/packages/flet/src/flet/controls/services/service.py b/sdk/python/packages/flet/src/flet/controls/services/service.py index c542d51530..bcc080862a 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/service.py +++ b/sdk/python/packages/flet/src/flet/controls/services/service.py @@ -9,7 +9,11 @@ @dataclass(kw_only=True) class Service(BaseControl): + """ + Base class for user services. + """ + def init(self): super().init() with contextlib.suppress(RuntimeError): - context.page._user_services.register_service(self) + context.page._services.register_service(self) diff --git a/sdk/python/packages/flet/src/flet/controls/services/share.py b/sdk/python/packages/flet/src/flet/controls/services/share.py new file mode 100644 index 0000000000..60c39ba7d0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/share.py @@ -0,0 +1,325 @@ +from collections.abc import Iterable +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.services.service import Service +from flet.controls.transform import Offset +from flet.utils.from_dict import from_dict + +__all__ = [ + "Share", + "ShareCupertinoActivityType", + "ShareFile", + "ShareResult", + "ShareResultStatus", +] + + +class ShareResultStatus(Enum): + """Outcome of a share operation.""" + + SUCCESS = "success" + """The user selected an action.""" + + DISMISSED = "dismissed" + """The user dismissed the share sheet.""" + + UNAVAILABLE = "unavailable" + """Platform cannot report a definite result.""" + + +class ShareCupertinoActivityType(Enum): + """iOS/macOS activity types that can be excluded from the share sheet.""" + + POST_TO_FACEBOOK = "postToFacebook" + """Share to Facebook.""" + + POST_TO_TWITTER = "postToTwitter" + """Share to Twitter.""" + + POST_TO_WEIBO = "postToWeibo" + """Share to Weibo.""" + + MESSAGE = "message" + """Share via Messages.""" + + MAIL = "mail" + """Share via Mail.""" + + PRINT = "print" + """Send to printer.""" + + COPY_TO_PASTEBOARD = "copyToPasteboard" + """Copy to clipboard.""" + + ASSIGN_TO_CONTACT = "assignToContact" + """Assign to contact.""" + + SAVE_TO_CAMERA_ROLL = "saveToCameraRoll" + """Save to camera roll.""" + + ADD_TO_READING_LIST = "addToReadingList" + """Add to reading list.""" + + POST_TO_FLICKR = "postToFlickr" + """Post to Flickr.""" + + POST_TO_VIMEO = "postToVimeo" + """Post to Vimeo.""" + + POST_TO_TENCENT_WEIBO = "postToTencentWeibo" + """Post to Tencent Weibo.""" + + AIR_DROP = "airDrop" + """AirDrop.""" + + OPEN_IN_IBOOKS = "openInIBooks" + """Open in iBooks.""" + + MARKUP_AS_PDF = "markupAsPDF" + """Markup as PDF.""" + + SHARE_PLAY = "sharePlay" + """SharePlay.""" + + COLLABORATION_INVITE_WITH_LINK = "collaborationInviteWithLink" + """Collaboration invite with link.""" + + COLLABORATION_COPY_LINK = "collaborationCopyLink" + """Collaboration copy link.""" + + ADD_TO_HOME_SCREEN = "addToHomeScreen" + """Add to home screen.""" + + +@dataclass +class ShareResult: + """Result returned from the native share sheet.""" + + status: ShareResultStatus + """The outcome of the share operation.""" + raw: str + """The raw string result from the platform.""" + + +@dataclass +class ShareFile: + """Represents a file to share, either from disk or in-memory bytes.""" + + path: Optional[str] = None + """Filesystem path to the file to share.""" + + data: Optional[bytes] = None + """Raw bytes of the file to share.""" + + mime_type: Optional[str] = None + """MIME type of the file.""" + + name: Optional[str] = None + """Optional name of the file.""" + + def __post_init__(self): + if self.path is None and self.data is None: + raise ValueError("Either 'path' or 'data' must be provided.") + + @staticmethod + def from_path(path: str, *, name: Optional[str] = None) -> "ShareFile": + """ + Create ShareFile from a filesystem path. + + Args: + path: Filesystem path to the file. + name: Optional name of the file. + """ + return ShareFile(path=path, name=name) + + @staticmethod + def from_bytes( + data: bytes, *, mime_type: Optional[str] = None, name: Optional[str] = None + ) -> "ShareFile": + """ + Create ShareFile from raw bytes. + + Args: + data: Raw bytes of the file. + mime_type: Optional MIME type of the file. + name: Optional name of the file. + """ + return ShareFile(data=data, mime_type=mime_type, name=name) + + +@dataclass +@control("Share") +class Share(Service): + """ + Shares text, links, or files using the platform share sheet. + """ + + async def share_text( + self, + text: str, + *, + title: Optional[str] = None, + subject: Optional[str] = None, + preview_thumbnail: Optional[ShareFile] = None, + share_position_origin: Optional[Offset] = None, + download_fallback_enabled: bool = True, + mail_to_fallback_enabled: bool = True, + excluded_cupertino_activities: Optional[ + Iterable[ShareCupertinoActivityType] + ] = None, + ) -> ShareResult: + """ + Share plain text with optional subject, title, and thumbnail. + + Args: + text: The text to share. + title: Optional title for the share sheet. + subject: Optional subject for the shared text. + preview_thumbnail: Optional thumbnail file to show in the share sheet. + share_position_origin: Optional position origin for the share sheet. + download_fallback_enabled: Whether to enable download fallback. + mail_to_fallback_enabled: Whether to enable mailto fallback. + excluded_cupertino_activities: Optional list of iOS/macOS activities + to exclude. + """ + + result = await self._invoke_method( + "share_text", + _share_args( + text=text, + title=title, + subject=subject, + preview_thumbnail=preview_thumbnail, + share_position_origin=share_position_origin, + download_fallback_enabled=download_fallback_enabled, + mail_to_fallback_enabled=mail_to_fallback_enabled, + excluded_cupertino_activities=excluded_cupertino_activities, + ), + ) + return from_dict(ShareResult, result) + + async def share_uri( + self, + uri: str, + *, + share_position_origin: Optional[Offset] = None, + excluded_cupertino_activities: Optional[ + Iterable[ShareCupertinoActivityType] + ] = None, + ) -> ShareResult: + """ + Share a link/URI. + + Args: + uri: The URI to share. + share_position_origin: Optional position origin for the share sheet. + excluded_cupertino_activities: Optional list of iOS/macOS activities + to exclude. + """ + + result = await self._invoke_method( + "share_uri", + _share_args( + uri=uri, + share_position_origin=share_position_origin, + excluded_cupertino_activities=excluded_cupertino_activities, + ), + ) + return from_dict(ShareResult, result) + + async def share_files( + self, + files: list[ShareFile], + *, + title: Optional[str] = None, + text: Optional[str] = None, + subject: Optional[str] = None, + preview_thumbnail: Optional[ShareFile] = None, + share_position_origin: Optional[Offset] = None, + download_fallback_enabled: bool = True, + mail_to_fallback_enabled: bool = True, + excluded_cupertino_activities: Optional[ + Iterable[ShareCupertinoActivityType] + ] = None, + ) -> ShareResult: + """ + Share one or more files with optional text/metadata. + + Args: + files: List of ShareFile instances to share. + title: Optional title for the share sheet. + text: Optional text to accompany the files. + subject: Optional subject for the shared files. + preview_thumbnail: Optional thumbnail file to show in the share sheet. + share_position_origin: Optional position origin for the share sheet. + download_fallback_enabled: Whether to enable download fallback. + mail_to_fallback_enabled: Whether to enable mailto fallback. + excluded_cupertino_activities: Optional list of iOS/macOS activities + to exclude. + """ + + if not files: + raise ValueError("files cannot be empty.") + + result = await self._invoke_method( + "share_files", + _share_args( + files=files, + title=title, + text=text, + subject=subject, + preview_thumbnail=preview_thumbnail, + share_position_origin=share_position_origin, + download_fallback_enabled=download_fallback_enabled, + mail_to_fallback_enabled=mail_to_fallback_enabled, + excluded_cupertino_activities=excluded_cupertino_activities, + ), + ) + return from_dict(ShareResult, result) + + +def _share_args( + *, + text: Optional[str] = None, + uri: Optional[str] = None, + title: Optional[str] = None, + subject: Optional[str] = None, + files: Optional[list[ShareFile]] = None, + preview_thumbnail: Optional[ShareFile] = None, + share_position_origin: Optional[Offset] = None, + download_fallback_enabled: bool = True, + mail_to_fallback_enabled: bool = True, + excluded_cupertino_activities: Optional[ + Iterable[ShareCupertinoActivityType] + ] = None, +): + args: dict = {} + if text is not None: + args["text"] = text + if uri is not None: + args["uri"] = uri + if title is not None: + args["title"] = title + if subject is not None: + args["subject"] = subject + if files is not None: + args["files"] = files + if preview_thumbnail is not None: + args["preview_thumbnail"] = preview_thumbnail + if share_position_origin is not None: + args["share_position_origin"] = { + "x": share_position_origin.x, + "y": share_position_origin.y, + } + args["download_fallback_enabled"] = download_fallback_enabled + args["mail_to_fallback_enabled"] = mail_to_fallback_enabled + + if excluded_cupertino_activities: + args["excluded_cupertino_activities"] = [ + a.value for a in excluded_cupertino_activities + ] + + return args diff --git a/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py b/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py index 9ae5796c3c..8ab4c5e573 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py +++ b/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py @@ -8,22 +8,44 @@ @control("SharedPreferences") class SharedPreferences(Service): + """ + Provides access to persistent key-value storage. + """ + async def set(self, key: str, value: Any) -> bool: + """ + Sets a value for the given key. + """ if value is None: raise ValueError("value can't be None") return await self._invoke_method("set", {"key": key, "value": value}) async def get(self, key: str): + """ + Gets the value for the given key. + """ return await self._invoke_method("get", {"key": key}) async def contains_key(self, key: str) -> bool: + """ + Checks if the given key exists. + """ return await self._invoke_method("contains_key", {"key": key}) async def remove(self, key: str) -> bool: + """ + Removes the value for the given key. + """ return await self._invoke_method("remove", {"key": key}) async def get_keys(self, key_prefix: str) -> list[str]: + """ + Gets all keys with the given prefix. + """ return await self._invoke_method("get_keys", {"key_prefix": key_prefix}) async def clear(self) -> bool: + """ + Clears all keys and values. + """ return await self._invoke_method("clear") diff --git a/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py b/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py index 6cc6add7b0..10ebcdec30 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py +++ b/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py @@ -11,6 +11,8 @@ @control("StoragePaths") class StoragePaths(Service): """ + Provides access to commonly used storage paths on the device. + Note: Its methods are not supported in web mode. """ diff --git a/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py b/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py index 989a311ecf..f55519593e 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py +++ b/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py @@ -1,41 +1,99 @@ +from dataclasses import dataclass, field +from enum import Enum from typing import Optional, Union from flet.controls.base_control import control from flet.controls.services.service import Service -from flet.controls.types import Url, UrlTarget +from flet.controls.types import Url -__all__ = ["UrlLauncher"] +__all__ = [ + "BrowserConfiguration", + "LaunchMode", + "UrlLauncher", + "WebViewConfiguration", +] + + +class LaunchMode(Enum): + """ + Preferred launch mode for opening a URL. + """ + + PLATFORM_DEFAULT = "platformDefault" + """Platform decides how to open the URL.""" + + IN_APP_WEB_VIEW = "inAppWebView" + """Load the URL inside an in-app web view.""" + + IN_APP_BROWSER_VIEW = "inAppBrowserView" + """Load the URL inside an in-app browser view (e.g., custom tabs).""" + + EXTERNAL_APPLICATION = "externalApplication" + """Pass the URL to another application to handle.""" + + EXTERNAL_NON_BROWSER_APPLICATION = "externalNonBrowserApplication" + """Pass the URL to a non-browser application to handle.""" + + +@dataclass +class WebViewConfiguration: + """ + Configuration options for in-app web views. + """ + + enable_javascript: bool = True + """Whether JavaScript execution is allowed.""" + + enable_dom_storage: bool = True + """Whether DOM storage is enabled.""" + + headers: dict[str, str] = field(default_factory=dict) + """Additional HTTP headers to include with the request.""" + + +@dataclass +class BrowserConfiguration: + """ + Configuration options for in-app browser views. + """ + + show_title: bool = False + """Whether the browser view should display the page title.""" @control("UrlLauncher") class UrlLauncher(Service): + """ + Provides access to URL launching capabilities. + """ + async def launch_url( self, url: Union[str, Url], *, - web_popup_window_name: Optional[Union[str, UrlTarget]] = None, - web_popup_window: bool = False, - web_popup_window_width: Optional[int] = None, - web_popup_window_height: Optional[int] = None, - ) -> None: + mode: LaunchMode = LaunchMode.PLATFORM_DEFAULT, + web_view_configuration: Optional[WebViewConfiguration] = None, + browser_configuration: Optional[BrowserConfiguration] = None, + web_only_window_name: Optional[str] = None, + ): """ - Opens a web browser or popup window to a given `url`. + Opens a web browser or in-app view to a given `url`. Args: url: The URL to open. - web_popup_window_name: Window tab/name to open URL in. - web_popup_window: Whether to open the URL in a browser popup window. - web_popup_window_width: Popup window width. - web_popup_window_height: Popup window height. + mode: Preferred launch mode for opening the URL. + web_view_configuration: Optional configuration for in-app web views. + browser_configuration: Optional configuration for in-app browser views. + web_only_window_name: Window name for web-only launches. """ await self._invoke_method( "launch_url", { "url": url, - "web_popup_window_name": web_popup_window_name, - "web_popup_window": web_popup_window, - "web_popup_window_width": web_popup_window_width, - "web_popup_window_height": web_popup_window_height, + "mode": mode, + "web_view_configuration": web_view_configuration, + "browser_configuration": browser_configuration, + "web_only_window_name": web_only_window_name, }, ) @@ -49,26 +107,76 @@ async def can_launch_url(self, url: Union[str, Url]) -> bool: Returns: `True` if it is possible to verify that there is a handler available. - `False` if there is no handler available, - or the application does not have permission to check. For example: - - - On recent versions of Android and iOS, this will always return `False` - unless the application has been configuration to allow querying the - system for launch support. - - On web, this will always return `False` except for a few specific schemes - that are always assumed to be supported (such as http(s)), as web pages - are never allowed to query installed applications. + `False` if there is no handler available, + or the application does not have permission to check. For example: + + - On recent versions of Android and iOS, this will always return `False` + unless the application has been configuration to allow querying the + system for launch support. + - On web, this will always return `False` except for a few specific + schemes that are always assumed to be supported (such as http(s)), + as web pages are never allowed to query installed applications. """ return await self._invoke_method( "can_launch_url", {"url": url}, ) - async def close_in_app_web_view(self) -> None: + async def close_in_app_web_view(self): """ Closes the in-app web view if it is currently open. - - This method invokes the platform-specific functionality to close - any web view that was previously opened within the application. """ await self._invoke_method("close_in_app_web_view") + + async def open_window( + self, + url: Union[str, Url], + *, + title: Optional[str] = None, + width: Optional[float] = None, + height: Optional[float] = None, + ): + """ + Opens a popup browser window in web environments. + + Args: + url: The URL to open in the popup window. + title: The popup window title. + width: Desired popup width in logical pixels. + height: Desired popup height in logical pixels. + """ + await self._invoke_method( + "open_window", + { + "url": url, + "title": title, + "width": width, + "height": height, + }, + ) + + async def supports_launch_mode(self, mode: LaunchMode) -> bool: + """ + Checks whether the specified launch mode is supported. + + Args: + mode: Launch mode to verify. + + Returns: + `True` if the launch mode is supported by the platform; otherwise `False`. + """ + return await self._invoke_method("supports_launch_mode", {"mode": mode}) + + async def supports_close_for_launch_mode(self, mode: LaunchMode) -> bool: + """ + Checks whether `close_in_app_web_view` is supported for a launch mode. + + Args: + mode: Launch mode to verify close support for. + + Returns: + `True` if closing an in-app web view is supported; otherwise `False`. + """ + return await self._invoke_method( + "supports_close_for_launch_mode", {"mode": mode} + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py new file mode 100644 index 0000000000..7405a28999 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -0,0 +1,89 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, EventHandler +from flet.controls.duration import Duration +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.sensor_error_event import SensorErrorEvent +from flet.controls.services.service import Service + +__all__ = ["UserAccelerometer", "UserAccelerometerReadingEvent"] + + +@dataclass(kw_only=True) +class UserAccelerometerReadingEvent(Event["UserAccelerometer"]): + """ + Like [`AccelerometerReadingEvent`][flet.], this is a discrete reading from + an accelerometer and measures the velocity of the device. However, + unlike [`AccelerometerReadingEvent`][flet.], this event does not include + the effects of gravity. + """ + + x: float + """Linear acceleration along the X axis, gravity removed, in `m/s^2`.""" + + y: float + """Linear acceleration along the Y axis, gravity removed, in `m/s^2`.""" + + z: float + """Linear acceleration along the Z axis, gravity removed, in `m/s^2`.""" + + timestamp: datetime + """Event timestamp.""" + + +@control("UserAccelerometer") +class UserAccelerometer(Service): + """ + Streams linear acceleration readings. + + If the device is still, or is moving along a straight line at constant speed, + the reported acceleration is zero. If the device is moving e.g. towards north + and its speed is increasing, the reported acceleration is towards north; + if it is slowing down, the reported acceleration is towards south; + if it is turning right, the reported acceleration is towards east. + The data of this stream is obtained by filtering out the effect of gravity + from [`AccelerometerReadingEvent`][flet.]. + + Note: + * Supported platforms: Android, iOS and web. + * Web ignores requested sampling intervals. + """ + + enabled: bool = True + """ + Whether the sensor should be sampled. Disable to stop streaming. + """ + + interval: Optional[Duration] = None + """ + Desired sampling interval provided as a [`Duration`][flet.Duration]. + Defaults to 200 ms. + """ + + cancel_on_error: bool = True + """ + Whether the stream subscription should cancel on the first sensor error. + """ + + on_reading: Optional[EventHandler[UserAccelerometerReadingEvent]] = None + """ + Fires when a new reading is available. + + `event` contains `x`, `y`, `z` acceleration values and `timestamp` + (microseconds since epoch). + """ + + on_error: Optional[EventHandler[SensorErrorEvent]] = None + """ + Fired when the platform reports a sensor error. `event.message` is the error + description. + """ + + def before_update(self): + if not (self.page.web or self.page.platform.is_mobile()): + raise FletUnsupportedPlatformException( + f"{self.__class__.__name__} is only supported on Android, iOS and web." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/wakelock.py b/sdk/python/packages/flet/src/flet/controls/services/wakelock.py new file mode 100644 index 0000000000..e22962a730 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/wakelock.py @@ -0,0 +1,32 @@ +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["Wakelock"] + + +@control("Wakelock") +class Wakelock(Service): + """ + Prevents the device from sleeping while enabled. + """ + + async def enable(self): + """ + Keeps the device awake. + """ + + await self._invoke_method("enable") + + async def disable(self): + """ + Allows the device to sleep again. + """ + + await self._invoke_method("disable") + + async def is_enabled(self) -> bool: + """ + Returns `True` if the wakelock is currently enabled. + """ + + return await self._invoke_method("is_enabled") diff --git a/sdk/python/packages/flet/src/flet/controls/types.py b/sdk/python/packages/flet/src/flet/controls/types.py index 57d00de325..b9b798ebdc 100644 --- a/sdk/python/packages/flet/src/flet/controls/types.py +++ b/sdk/python/packages/flet/src/flet/controls/types.py @@ -198,7 +198,7 @@ def __post_init__(self): class ResponsiveRowBreakpoint(Enum): """ Breakpoint names used by [`ResponsiveRow`][flet.] and responsive - properties such as [`Control.col`][flet.Control.]. + properties such as [`Control.col`][flet.]. To define custom breakpoints, see [`ResponsiveRow.breakpoints`][flet.]. """ diff --git a/sdk/python/packages/flet/src/flet/messaging/session.py b/sdk/python/packages/flet/src/flet/messaging/session.py index a6802925a6..c1cd691737 100644 --- a/sdk/python/packages/flet/src/flet/messaging/session.py +++ b/sdk/python/packages/flet/src/flet/messaging/session.py @@ -258,7 +258,7 @@ async def after_event(self, control: BaseControl | None): await self.__auto_update(control) # unregister unreferenced services - self.page._user_services.unregister_services() + self.page._services.unregister_services() async def __auto_update(self, control: BaseControl | None): while control: diff --git a/sdk/python/packages/flet/tests/test_object_diff_in_place.py b/sdk/python/packages/flet/tests/test_object_diff_in_place.py index d0f952c4cf..f2dc89cfbc 100644 --- a/sdk/python/packages/flet/tests/test_object_diff_in_place.py +++ b/sdk/python/packages/flet/tests/test_object_diff_in_place.py @@ -113,7 +113,7 @@ def test_simple_page(): page.bgcolor = Colors.GREEN page.fonts = {"font1": "font_url_1", "font2": "font_url_2"} page.on_login = lambda e: print("on login") - page._user_services._services.append(MyService(prop_1="Hello", prop_2=[1, 2, 3])) + page._services._services.append(MyService(prop_1="Hello", prop_2=[1, 2, 3])) # page and window have hard-coded IDs assert page._i == 1 @@ -121,14 +121,11 @@ def test_simple_page(): msg, _, _, added_controls, removed_controls = make_msg(page, {}, show_details=True) u_msg = b_unpack(msg) - assert len(added_controls) == 13 + assert len(added_controls) == 8 assert len(removed_controls) == 0 assert page.parent is None assert page.controls[0].parent == page.views[0] - assert page.clipboard - assert page.clipboard.parent - assert page.clipboard.page print(u_msg) @@ -172,7 +169,7 @@ def test_simple_page(): # "clipboard": {"_i": 23, "_c": "Clipboard"}, # "storage_paths": {"_i": 24, "_c": "StoragePaths"}, # "url_launcher": {"_i": 25, "_c": "UrlLauncher"}, - # "_user_services": { + # "_services": { # "_i": 26, # "_c": "ServiceRegistry", # "services": [ @@ -184,17 +181,6 @@ def test_simple_page(): # } # ], # }, - # "_page_services": { - # "_i": 27, - # "_c": "ServiceRegistry", - # "services": [ - # {"_i": 21, "_c": "BrowserContextMenu"}, - # {"_i": 22, "_c": "SharedPreferences"}, - # {"_i": 23, "_c": "Clipboard"}, - # {"_i": 25, "_c": "UrlLauncher"}, - # {"_i": 24, "_c": "StoragePaths"}, - # ], - # }, # "fonts": {"font1": "font_url_1", "font2": "font_url_2"}, # "on_login": True, # }, @@ -218,7 +204,7 @@ def test_simple_page(): with raises(RuntimeError): assert page.controls[0].controls[0].page is None - page._user_services._services[0].prop_2 = [2, 6] + page._services._services[0].prop_2 = [2, 6] # add 2 new buttons to a list _, patch, _, added_controls, removed_controls = make_msg(page, show_details=True) @@ -243,17 +229,17 @@ def test_simple_page(): {"op": "remove", "path": ["fonts", "font2"], "value": "font_url_2"}, { "op": "remove", - "path": ["_user_services", "_services", 0, "prop_2", 0], + "path": ["_services", "_services", 0, "prop_2", 0], "value": 1, }, { "op": "add", - "path": ["_user_services", "_services", 0, "prop_2", 1], + "path": ["_services", "_services", 0, "prop_2", 1], "value": 6, }, { "op": "remove", - "path": ["_user_services", "_services", 0, "prop_2", 2], + "path": ["_services", "_services", 0, "prop_2", 2], "value": 3, }, ],