From 4fde4e06011a8938566f465a475ad053f7e32f74 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 20 Nov 2025 12:22:13 -0800 Subject: [PATCH 01/33] Add sensor services and events to Flet Introduced support for device sensors including accelerometer, user accelerometer, gyroscope, magnetometer, and barometer. Added corresponding Dart services, Python control classes, event types, and documentation. Updated mkdocs navigation and provided example usage for each sensor. --- .../flet/lib/src/flet_core_extension.dart | 11 + packages/flet/lib/src/services/sensors.dart | 198 ++++++++++++++++++ .../controls/sensors/accelerometer/basic.py | 28 +++ .../controls/sensors/barometer/basic.py | 27 +++ .../controls/sensors/gyroscope/basic.py | 27 +++ .../controls/sensors/magnetometer/basic.py | 27 +++ .../sensors/user_accelerometer/basic.py | 30 +++ .../flet/docs/controls/accelerometer.md | 14 ++ .../packages/flet/docs/controls/barometer.md | 14 ++ .../packages/flet/docs/controls/gyroscope.md | 14 ++ .../flet/docs/controls/magnetometer.md | 14 ++ .../flet/docs/controls/useraccelerometer.md | 14 ++ .../docs/types/accelerometerreadingevent.md | 5 + .../flet/docs/types/barometerreadingevent.md | 5 + .../flet/docs/types/gyroscopereadingevent.md | 5 + .../docs/types/magnetometerreadingevent.md | 5 + .../flet/docs/types/sensorerrorevent.md | 5 + .../types/useraccelerometerreadingevent.md | 5 + sdk/python/packages/flet/mkdocs.yml | 11 + sdk/python/packages/flet/src/flet/__init__.py | 24 +++ .../flet/controls/services/accelerometer.py | 71 +++++++ .../src/flet/controls/services/barometer.py | 56 +++++ .../src/flet/controls/services/gyroscope.py | 54 +++++ .../flet/controls/services/magnetometer.py | 55 +++++ .../flet/controls/services/sensor_events.py | 153 ++++++++++++++ .../controls/services/user_accelerometer.py | 61 ++++++ 26 files changed, 933 insertions(+) create mode 100644 packages/flet/lib/src/services/sensors.dart create mode 100644 sdk/python/examples/controls/sensors/accelerometer/basic.py create mode 100644 sdk/python/examples/controls/sensors/barometer/basic.py create mode 100644 sdk/python/examples/controls/sensors/gyroscope/basic.py create mode 100644 sdk/python/examples/controls/sensors/magnetometer/basic.py create mode 100644 sdk/python/examples/controls/sensors/user_accelerometer/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/accelerometer.md create mode 100644 sdk/python/packages/flet/docs/controls/barometer.md create mode 100644 sdk/python/packages/flet/docs/controls/gyroscope.md create mode 100644 sdk/python/packages/flet/docs/controls/magnetometer.md create mode 100644 sdk/python/packages/flet/docs/controls/useraccelerometer.md create mode 100644 sdk/python/packages/flet/docs/types/accelerometerreadingevent.md create mode 100644 sdk/python/packages/flet/docs/types/barometerreadingevent.md create mode 100644 sdk/python/packages/flet/docs/types/gyroscopereadingevent.md create mode 100644 sdk/python/packages/flet/docs/types/magnetometerreadingevent.md create mode 100644 sdk/python/packages/flet/docs/types/sensorerrorevent.md create mode 100644 sdk/python/packages/flet/docs/types/useraccelerometerreadingevent.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/accelerometer.py create mode 100644 sdk/python/packages/flet/src/flet/controls/services/barometer.py create mode 100644 sdk/python/packages/flet/src/flet/controls/services/gyroscope.py create mode 100644 sdk/python/packages/flet/src/flet/controls/services/magnetometer.py create mode 100644 sdk/python/packages/flet/src/flet/controls/services/sensor_events.py create mode 100644 sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index 17e0868ff5..a024887349 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -112,6 +112,7 @@ import 'services/file_picker.dart'; import 'services/haptic_feedback.dart'; import 'services/semantics_service.dart'; import 'services/shake_detector.dart'; +import 'services/sensors.dart'; import 'services/shared_preferences.dart'; import 'services/storage_paths.dart'; import 'services/tester.dart'; @@ -369,22 +370,32 @@ 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 "Clipboard": return ClipboardService(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 "StoragePaths": return StoragePaths(control: control); case "Tester": return TesterService(control: control); + case "UserAccelerometer": + return UserAccelerometerService(control: control); case "UrlLauncher": return UrlLauncherService(control: control); default: diff --git a/packages/flet/lib/src/services/sensors.dart b/packages/flet/lib/src/services/sensors.dart new file mode 100644 index 0000000000..01745fc1ee --- /dev/null +++ b/packages/flet/lib/src/services/sensors.dart @@ -0,0 +1,198 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:sensors_plus/sensors_plus.dart'; + +import '../flet_service.dart'; +import '../utils/time.dart'; + +abstract class _SensorStreamService extends FletService { + _SensorStreamService({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(); + } +} + +class AccelerometerService extends _SensorStreamService { + 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.microsecondsSinceEpoch, + }; + } +} + +class UserAccelerometerService + extends _SensorStreamService { + 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, + }; + } +} + +class GyroscopeService extends _SensorStreamService { + 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.microsecondsSinceEpoch, + }; + } +} + +class MagnetometerService extends _SensorStreamService { + 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.microsecondsSinceEpoch, + }; + } +} + +class BarometerService extends _SensorStreamService { + 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.microsecondsSinceEpoch, + }; + } +} diff --git a/sdk/python/examples/controls/sensors/accelerometer/basic.py b/sdk/python/examples/controls/sensors/accelerometer/basic.py new file mode 100644 index 0000000000..ce2159bba3 --- /dev/null +++ b/sdk/python/examples/controls/sensors/accelerometer/basic.py @@ -0,0 +1,28 @@ +import flet as ft + + +def main(page: ft.Page): + intro = ft.Text("Move your device to see accelerometer readings.") + reading = ft.Text("Waiting for data...") + + 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.session.store.set( + "accelerometer_service", + ft.Accelerometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=100), + cancel_on_error=False, + ), + ) + + page.add(intro, reading) + + +ft.run(main) diff --git a/sdk/python/examples/controls/sensors/barometer/basic.py b/sdk/python/examples/controls/sensors/barometer/basic.py new file mode 100644 index 0000000000..d593133f75 --- /dev/null +++ b/sdk/python/examples/controls/sensors/barometer/basic.py @@ -0,0 +1,27 @@ +import flet as ft + + +def main(page: ft.Page): + intro = ft.Text("Atmospheric pressure (hPa).") + reading = ft.Text("Waiting for data...") + + 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.session.store.set( + "barometer_service", + ft.Barometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=500), + ), + ) + + page.add(intro, reading) + + +ft.run(main) diff --git a/sdk/python/examples/controls/sensors/gyroscope/basic.py b/sdk/python/examples/controls/sensors/gyroscope/basic.py new file mode 100644 index 0000000000..5c3d090723 --- /dev/null +++ b/sdk/python/examples/controls/sensors/gyroscope/basic.py @@ -0,0 +1,27 @@ +import flet as ft + + +def main(page: ft.Page): + intro = ft.Text("Rotate your device to see gyroscope readings.") + reading = ft.Text("Waiting for data...") + + 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.session.store.set( + "gyroscope_service", + ft.Gyroscope( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=50), + ), + ) + + page.add(intro, reading) + + +ft.run(main) diff --git a/sdk/python/examples/controls/sensors/magnetometer/basic.py b/sdk/python/examples/controls/sensors/magnetometer/basic.py new file mode 100644 index 0000000000..bf8ba903d9 --- /dev/null +++ b/sdk/python/examples/controls/sensors/magnetometer/basic.py @@ -0,0 +1,27 @@ +import flet as ft + + +def main(page: ft.Page): + intro = ft.Text("Monitor the ambient magnetic field (uT).") + reading = ft.Text("Waiting for data...") + + 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.session.store.set( + "magnetometer_service", + ft.Magnetometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=200), + ), + ) + + page.add(intro, reading) + + +ft.run(main) diff --git a/sdk/python/examples/controls/sensors/user_accelerometer/basic.py b/sdk/python/examples/controls/sensors/user_accelerometer/basic.py new file mode 100644 index 0000000000..1f0d31ab87 --- /dev/null +++ b/sdk/python/examples/controls/sensors/user_accelerometer/basic.py @@ -0,0 +1,30 @@ +import flet as ft + + +def main(page: ft.Page): + intro = ft.Text( + "Linear acceleration without gravity. " + "Keep the app running on a device with motion sensors." + ) + reading = ft.Text("Waiting for data...") + + 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.session.store.set( + "user_accelerometer_service", + ft.UserAccelerometer( + on_reading=handle_reading, + on_error=handle_error, + interval=ft.Duration(milliseconds=100), + ), + ) + + page.add(intro, reading) + + +ft.run(main) diff --git a/sdk/python/packages/flet/docs/controls/accelerometer.md b/sdk/python/packages/flet/docs/controls/accelerometer.md new file mode 100644 index 0000000000..badc93a570 --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/accelerometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Accelerometer +examples: ../../examples/controls/sensors/accelerometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/barometer.md b/sdk/python/packages/flet/docs/controls/barometer.md new file mode 100644 index 0000000000..154b46056d --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/barometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Barometer +examples: ../../examples/controls/sensors/barometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/gyroscope.md b/sdk/python/packages/flet/docs/controls/gyroscope.md new file mode 100644 index 0000000000..2635bb8cb0 --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/gyroscope.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Gyroscope +examples: ../../examples/controls/sensors/gyroscope +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/magnetometer.md b/sdk/python/packages/flet/docs/controls/magnetometer.md new file mode 100644 index 0000000000..f81db5356f --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/magnetometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Magnetometer +examples: ../../examples/controls/sensors/magnetometer +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/controls/useraccelerometer.md b/sdk/python/packages/flet/docs/controls/useraccelerometer.md new file mode 100644 index 0000000000..9e2b9d496a --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/useraccelerometer.md @@ -0,0 +1,14 @@ +--- +class_name: flet.UserAccelerometer +examples: ../../examples/controls/sensors/user_accelerometer +--- + +{{ 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/types/barometerreadingevent.md b/sdk/python/packages/flet/docs/types/barometerreadingevent.md new file mode 100644 index 0000000000..ebed8768c0 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/barometerreadingevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.BarometerReadingEvent +--- + +{{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/gyroscopereadingevent.md b/sdk/python/packages/flet/docs/types/gyroscopereadingevent.md new file mode 100644 index 0000000000..f6b563fffb --- /dev/null +++ b/sdk/python/packages/flet/docs/types/gyroscopereadingevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.GyroscopeReadingEvent +--- + +{{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/magnetometerreadingevent.md b/sdk/python/packages/flet/docs/types/magnetometerreadingevent.md new file mode 100644 index 0000000000..765426b66b --- /dev/null +++ b/sdk/python/packages/flet/docs/types/magnetometerreadingevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.MagnetometerReadingEvent +--- + +{{ class_all_options(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/sensorerrorevent.md b/sdk/python/packages/flet/docs/types/sensorerrorevent.md new file mode 100644 index 0000000000..0e483bd3a2 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/sensorerrorevent.md @@ -0,0 +1,5 @@ +--- +class_name: flet.SensorErrorEvent +--- + +{{ class_all_options(class_name) }} 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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index ee2f3985c9..6abc86c102 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -266,6 +266,7 @@ nav: # - NativeAd: ads/nativead.md - Audio: audio/index.md - AudioRecorder: audio_recorder/index.md + - Accelerometer: controls/accelerometer.md - AlertDialog: controls/alertdialog.md - AnimatedSwitcher: controls/animatedswitcher.md - AppBar: controls/appbar.md @@ -273,6 +274,7 @@ nav: - AutofillGroup: controls/autofillgroup.md - Badge: types/badge.md - Banner: controls/banner.md + - Barometer: controls/barometer.md - BottomAppBar: controls/bottomappbar.md - BottomSheet: controls/bottomsheet.md - Button: controls/button.md @@ -367,6 +369,7 @@ nav: - GestureDetector: controls/gesturedetector.md - Geolocator: geolocator/index.md - GridView: controls/gridview.md + - Gyroscope: controls/gyroscope.md - HapticFeedback: controls/hapticfeedback.md - Icon: controls/icon.md - IconButton: controls/iconbutton.md @@ -398,6 +401,7 @@ nav: - TextSourceAttribution: map/text_source_attribution.md - ImageSourceAttribution: map/image_source_attribution.md - Markdown: controls/markdown.md + - Magnetometer: controls/magnetometer.md - MenuBar: controls/menubar.md - MenuItemButton: controls/menuitembutton.md - MergeSemantics: controls/mergesemantics.md @@ -452,6 +456,7 @@ nav: - TextField: controls/textfield.md - TimePicker: controls/timepicker.md - TransparentPointer: controls/transparentpointer.md + - UserAccelerometer: controls/useraccelerometer.md - VerticalDivider: controls/verticaldivider.md - Video: video/index.md - View: controls/view.md @@ -833,10 +838,12 @@ 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 - CanvasResizeEvent: types/canvasresizeevent.md - ContextMenuDismissEvent: types/contextmenudismissevent.md - ContextMenuSelectEvent: types/contextmenuselectevent.md @@ -852,6 +859,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 @@ -860,6 +868,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 @@ -870,6 +879,7 @@ 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 @@ -878,6 +888,7 @@ nav: - 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 e2c8c87976..160ccc5e56 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -413,6 +413,8 @@ ScrollDirection, ScrollType, ) +from flet.controls.services.accelerometer import Accelerometer +from flet.controls.services.barometer import Barometer from flet.controls.services.browser_context_menu import BrowserContextMenu from flet.controls.services.clipboard import Clipboard from flet.controls.services.file_picker import ( @@ -422,13 +424,24 @@ FilePickerUploadEvent, FilePickerUploadFile, ) +from flet.controls.services.gyroscope import Gyroscope from flet.controls.services.haptic_feedback import HapticFeedback +from flet.controls.services.magnetometer import Magnetometer from flet.controls.services.semantics_service import Assertiveness, SemanticsService +from flet.controls.services.sensor_events import ( + AccelerometerReadingEvent, + BarometerReadingEvent, + GyroscopeReadingEvent, + MagnetometerReadingEvent, + SensorErrorEvent, + UserAccelerometerReadingEvent, +) from flet.controls.services.service import Service from flet.controls.services.shake_detector import ShakeDetector 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.user_accelerometer import UserAccelerometer from flet.controls.template_route import TemplateRoute from flet.controls.text_style import ( StrutStyle, @@ -538,6 +551,8 @@ from flet.pubsub.pubsub_hub import PubSubHub __all__ = [ + "Accelerometer", + "AccelerometerReadingEvent", "AdaptiveControl", "AlertDialog", "Alignment", @@ -568,6 +583,8 @@ "BadgeValue", "Banner", "BannerTheme", + "Barometer", + "BarometerReadingEvent", "BaseControl", "BasePage", "BeveledRectangleBorder", @@ -733,6 +750,8 @@ "Gradient", "GradientTileMode", "GridView", + "Gyroscope", + "GyroscopeReadingEvent", "HapticFeedback", "HoverEvent", "Icon", @@ -775,6 +794,8 @@ "LongPressMoveUpdateEvent", "LongPressStartEvent", "MacOsDeviceInfo", + "Magnetometer", + "MagnetometerReadingEvent", "MainAxisAlignment", "Margin", "MarginValue", @@ -887,6 +908,7 @@ "SelectionArea", "Semantics", "SemanticsService", + "SensorErrorEvent", "Service", "ShaderMask", "ShakeDetector", @@ -962,6 +984,8 @@ "Url", "UrlLauncher", "UrlTarget", + "UserAccelerometer", + "UserAccelerometerReadingEvent", "ValueKey", "VerticalAlignment", "VerticalDivider", 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..6d4836c394 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -0,0 +1,71 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_events import ( + AccelerometerReadingEvent, + SensorErrorEvent, +) +from flet.controls.services.service import Service + +__all__ = ["Accelerometer"] + + +@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, Web. Web ignores requested sampling + intervals and iOS apps must declare `NSMotionUsageDescription`. + """ + + 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. + """ 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..07bde35342 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/barometer.py @@ -0,0 +1,56 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_events import ( + BarometerReadingEvent, + SensorErrorEvent, +) +from flet.controls.services.service import Service + +__all__ = ["Barometer"] + + +@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 and iOS ignores custom 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, 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. + """ 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..90f31ce436 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -0,0 +1,54 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_events import ( + GyroscopeReadingEvent, + SensorErrorEvent, +) +from flet.controls.services.service import Service + +__all__ = ["Gyroscope"] + + +@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, Web (sampling interval + hints are ignored on Web). + """ + + 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. + """ 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..51a6973068 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py @@ -0,0 +1,55 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_events import ( + MagnetometerReadingEvent, + SensorErrorEvent, +) +from flet.controls.services.service import Service + +__all__ = ["Magnetometer"] + + +@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. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/services/sensor_events.py b/sdk/python/packages/flet/src/flet/controls/services/sensor_events.py new file mode 100644 index 0000000000..3464084b7b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/sensor_events.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from flet.controls.control_event import Event, EventControlType + +if TYPE_CHECKING: + from flet.controls.services.accelerometer import Accelerometer # noqa: F401 + from flet.controls.services.barometer import Barometer # noqa: F401 + from flet.controls.services.gyroscope import Gyroscope # noqa: F401 + from flet.controls.services.magnetometer import Magnetometer # noqa: F401 + from flet.controls.services.user_accelerometer import ( + UserAccelerometer, # noqa: F401 + ) + +__all__ = [ + "AccelerometerReadingEvent", + "BarometerReadingEvent", + "GyroscopeReadingEvent", + "MagnetometerReadingEvent", + "SensorErrorEvent", + "UserAccelerometerReadingEvent", +] + + +@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: int + """Event timestamp, expressed in microseconds since epoch.""" + + +@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: int + """Event timestamp, expressed in microseconds since epoch.""" + + +@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: int + """Event timestamp, expressed in microseconds since epoch.""" + + +@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: int + """Event timestamp, expressed in microseconds since epoch.""" + + +@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: int + """Event timestamp, expressed in microseconds since epoch.""" + + +@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/user_accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py new file mode 100644 index 0000000000..0e0c93e964 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -0,0 +1,61 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import EventHandler +from flet.controls.duration import Duration +from flet.controls.services.sensor_events import ( + SensorErrorEvent, + UserAccelerometerReadingEvent, +) +from flet.controls.services.service import Service + +__all__ = ["UserAccelerometer"] + + +@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, Web. Web ignores requested sampling + intervals and iOS apps must declare `NSMotionUsageDescription`. + """ + + 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. + """ From d393a6dd10bcdce65c6bfc51699d5e89f3e4e323 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 21 Nov 2025 18:01:09 -0800 Subject: [PATCH 02/33] Refactor sensors service imports and formatting Added import for numbers utility in sensors.dart and improved code formatting for error handling and debug print statements. --- packages/flet/lib/src/services/sensors.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/flet/lib/src/services/sensors.dart b/packages/flet/lib/src/services/sensors.dart index 01745fc1ee..2694cca56f 100644 --- a/packages/flet/lib/src/services/sensors.dart +++ b/packages/flet/lib/src/services/sensors.dart @@ -4,6 +4,7 @@ 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 _SensorStreamService extends FletService { @@ -80,8 +81,8 @@ abstract class _SensorStreamService extends FletService { }, onError: (error, stackTrace) { if (_hasErrorSubscribers) { - control.triggerEvent( - "error", {"message": error?.toString() ?? "Unknown sensor error"}); + control.triggerEvent("error", + {"message": error?.toString() ?? "Unknown sensor error"}); } else { debugPrint( "Error listening to ${control.type} sensor stream: $error"); @@ -90,8 +91,7 @@ abstract class _SensorStreamService extends FletService { cancelOnError: _cancelOnError, ); } catch (error) { - debugPrint( - "Failed to initialize ${control.type} sensor stream: $error"); + debugPrint("Failed to initialize ${control.type} sensor stream: $error"); } } From 81b23c65a774ded3f8787d2c38195efe4e8ed8fd Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 1 Dec 2025 18:00:07 -0800 Subject: [PATCH 03/33] Update PyPI cleanup script and add module support Changed the version exclusion regex and added cleanup commands for multiple flet modules to the clean-pypi.sh script to ensure all related packages are properly cleaned from PyPI. --- .github/scripts/clean-pypi.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 From 3d1310d67b274540400ce6fd91ac42dd01b6a7dd Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 2 Dec 2025 11:50:19 -0800 Subject: [PATCH 04/33] Update sensors_plus to version 7.0.0 Bump sensors_plus dependency from 6.1.1 to 7.0.0 in pubspec.yaml and update pubspec.lock accordingly for compatibility with the latest package features and fixes. --- client/pubspec.lock | 4 ++-- packages/flet/pubspec.yaml | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index bc1b237618..1ab72c29be 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1122,10 +1122,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: diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 996cb72e31..7abf5501ba 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.2 screenshot: ^3.0.0 - sensors_plus: ^6.1.1 + sensors_plus: ^7.0.0 shared_preferences: 2.5.3 shimmer: ^3.0.0 url_launcher: 6.3.2 @@ -50,10 +50,3 @@ 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 From 49e779fd4774484a7fda01735b1f722a09bbafdd Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 10:10:23 -0800 Subject: [PATCH 05/33] Add services property to BasePage and View Introduces a services property to BasePage and View, allowing management of Service controls via property accessors. The services attribute is now stored in View and proxied through BasePage, improving encapsulation and flexibility. --- .../packages/flet/src/flet/controls/base_page.py | 10 +++++++++- .../packages/flet/src/flet/controls/core/view.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) 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() From 564ffa51287dbcbf33c63427b63be775e592df81 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 10:15:17 -0800 Subject: [PATCH 06/33] Refactor sensor examples and update service docs Sensor example scripts now use `page.services` and `page.controls` instead of session store and `page.add`. Documentation for sensor services was updated for clarity, platform support, and iOS requirements, including details on `NSMotionUsageDescription` for barometer usage. --- .../controls/sensors/accelerometer/basic.py | 9 ++++--- .../controls/sensors/barometer/basic.py | 7 +++--- .../controls/sensors/gyroscope/basic.py | 9 ++++--- .../controls/sensors/magnetometer/basic.py | 7 +++--- .../sensors/user_accelerometer/basic.py | 7 +++--- .../flet/controls/services/accelerometer.py | 4 ++-- .../src/flet/controls/services/barometer.py | 24 ++++++++++++++++--- .../src/flet/controls/services/gyroscope.py | 4 ++-- .../flet/controls/services/magnetometer.py | 5 ++-- .../controls/services/user_accelerometer.py | 4 ++-- 10 files changed, 47 insertions(+), 33 deletions(-) diff --git a/sdk/python/examples/controls/sensors/accelerometer/basic.py b/sdk/python/examples/controls/sensors/accelerometer/basic.py index ce2159bba3..c46a3e69a7 100644 --- a/sdk/python/examples/controls/sensors/accelerometer/basic.py +++ b/sdk/python/examples/controls/sensors/accelerometer/basic.py @@ -12,17 +12,16 @@ def handle_reading(e: ft.AccelerometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Accelerometer error: {e.message}")) - page.session.store.set( - "accelerometer_service", + page.services = [ ft.Accelerometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=100), cancel_on_error=False, - ), - ) + ) + ] - page.add(intro, reading) + page.controls = [intro, reading] ft.run(main) diff --git a/sdk/python/examples/controls/sensors/barometer/basic.py b/sdk/python/examples/controls/sensors/barometer/basic.py index d593133f75..0e07fcb30b 100644 --- a/sdk/python/examples/controls/sensors/barometer/basic.py +++ b/sdk/python/examples/controls/sensors/barometer/basic.py @@ -12,16 +12,15 @@ def handle_reading(e: ft.BarometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Barometer error: {e.message}")) - page.session.store.set( - "barometer_service", + page.services = [ ft.Barometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=500), ), - ) + ] - page.add(intro, reading) + page.controls = [intro, reading] ft.run(main) diff --git a/sdk/python/examples/controls/sensors/gyroscope/basic.py b/sdk/python/examples/controls/sensors/gyroscope/basic.py index 5c3d090723..fe79154da9 100644 --- a/sdk/python/examples/controls/sensors/gyroscope/basic.py +++ b/sdk/python/examples/controls/sensors/gyroscope/basic.py @@ -12,16 +12,15 @@ def handle_reading(e: ft.GyroscopeReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Gyroscope error: {e.message}")) - page.session.store.set( - "gyroscope_service", + page.services = [ ft.Gyroscope( on_reading=handle_reading, on_error=handle_error, - interval=ft.Duration(milliseconds=50), + interval=ft.Duration(milliseconds=100), ), - ) + ] - page.add(intro, reading) + page.controls = [intro, reading] ft.run(main) diff --git a/sdk/python/examples/controls/sensors/magnetometer/basic.py b/sdk/python/examples/controls/sensors/magnetometer/basic.py index bf8ba903d9..f1d389d561 100644 --- a/sdk/python/examples/controls/sensors/magnetometer/basic.py +++ b/sdk/python/examples/controls/sensors/magnetometer/basic.py @@ -12,16 +12,15 @@ def handle_reading(e: ft.MagnetometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Magnetometer error: {e.message}")) - page.session.store.set( - "magnetometer_service", + page.services = [ ft.Magnetometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=200), ), - ) + ] - page.add(intro, reading) + page.controls = [intro, reading] ft.run(main) diff --git a/sdk/python/examples/controls/sensors/user_accelerometer/basic.py b/sdk/python/examples/controls/sensors/user_accelerometer/basic.py index 1f0d31ab87..655d90efec 100644 --- a/sdk/python/examples/controls/sensors/user_accelerometer/basic.py +++ b/sdk/python/examples/controls/sensors/user_accelerometer/basic.py @@ -15,16 +15,15 @@ def handle_reading(e: ft.UserAccelerometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"UserAccelerometer error: {e.message}")) - page.session.store.set( - "user_accelerometer_service", + page.services = [ ft.UserAccelerometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=100), ), - ) + ] - page.add(intro, reading) + page.controls = [intro, reading] ft.run(main) diff --git a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py index 6d4836c394..e3125f4818 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -33,8 +33,8 @@ class Accelerometer(Service): Accelerometer reports zero acceleration if the device is free falling. Note: - Supported platforms: Android, iOS, Web. Web ignores requested sampling - intervals and iOS apps must declare `NSMotionUsageDescription`. + * Supported platforms: Android, iOS, web. + * Web ignores requested sampling intervals. """ enabled: bool = True diff --git a/sdk/python/packages/flet/src/flet/controls/services/barometer.py b/sdk/python/packages/flet/src/flet/controls/services/barometer.py index 07bde35342..c5d4ff335f 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/barometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/barometer.py @@ -20,9 +20,27 @@ class Barometer(Service): and weather-related experiences. Note: - Supported platforms: Android, iOS. Barometer APIs are not exposed on the Web - or desktop platforms and iOS ignores custom sampling intervals. - """ + * 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 """ diff --git a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py index 90f31ce436..c6c4fd9e5e 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -19,8 +19,8 @@ class Gyroscope(Service): reporting device rotation rate around each axis in `rad/s`. Note: - Supported platforms: Android, iOS, Web (sampling interval - hints are ignored on Web). + * Supported platforms: Android, iOS, web. + * Web ignores requested sampling intervals. """ enabled: bool = True diff --git a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py index 51a6973068..664a5cf0f6 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py @@ -20,8 +20,9 @@ class Magnetometer(Service): 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. + * 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 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 index 0e0c93e964..b9d44651eb 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -26,8 +26,8 @@ class UserAccelerometer(Service): from [`AccelerometerReadingEvent`][flet.]. Note: - Supported platforms: Android, iOS, Web. Web ignores requested sampling - intervals and iOS apps must declare `NSMotionUsageDescription`. + * Supported platforms: Android, iOS, web. + * Web ignores requested sampling intervals. """ enabled: bool = True From cb20ac0ae67c159b0924663af1beb169959b14e8 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 10:34:52 -0800 Subject: [PATCH 07/33] Fix and clarify docstrings and references in controls Corrected docstring references and improved clarity in several control modules. Updated links to use correct attribute names and fixed formatting in flet_map/types.py, cupertino_switch.py, snack_bar.py, and types.py. --- sdk/python/packages/flet-map/src/flet_map/types.py | 2 +- .../flet/src/flet/controls/cupertino/cupertino_switch.py | 4 ++-- .../packages/flet/src/flet/controls/material/snack_bar.py | 2 +- sdk/python/packages/flet/src/flet/controls/types.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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/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/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.]. """ From 25787b1468e488c0ff06ad677cc55824514dfb17 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 11:49:32 -0800 Subject: [PATCH 08/33] Add ScreenBrightness service and Python bindings Introduces ScreenBrightnessService in Dart and ScreenBrightness control in Python, enabling control and observation of system and application screen brightness. Updates plugin registrants for macOS and Windows, adds documentation and example usage, and includes the screen_brightness dependency in pubspec.yaml. --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + client/pubspec.lock | 40 ++++++ .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../flet/lib/src/flet_core_extension.dart | 3 + .../lib/src/services/screen_brightness.dart | 134 ++++++++++++++++++ packages/flet/pubspec.yaml | 1 + .../controls/screen_brightness/basic.py | 98 +++++++++++++ .../flet/docs/controls/screenbrightness.md | 14 ++ sdk/python/packages/flet/mkdocs.yml | 1 + sdk/python/packages/flet/src/flet/__init__.py | 6 + .../controls/services/screen_brightness.py | 107 ++++++++++++++ 12 files changed, 410 insertions(+) create mode 100644 packages/flet/lib/src/services/screen_brightness.dart create mode 100644 sdk/python/examples/controls/screen_brightness/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/screenbrightness.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 18f1748490..1ef11de80d 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,6 +15,7 @@ import package_info_plus import path_provider_foundation import record_macos import rive_common +import screen_brightness_macos import screen_retriever_macos import shared_preferences_foundation import url_launcher_macos @@ -35,6 +36,7 @@ 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")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/client/pubspec.lock b/client/pubspec.lock index 1ab72c29be..285bcea2cf 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1054,6 +1054,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 +1070,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 +1102,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: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 754c15c2ff..4e4e19f6ce 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); RivePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("RivePlugin")); + ScreenBrightnessWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 72a5864a6b..8a2e3c09da 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows record_windows rive_common + screen_brightness_windows screen_retriever_windows url_launcher_windows volume_controller diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index a39a29d58e..c7ee6ab8d4 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -114,6 +114,7 @@ import 'services/semantics_service.dart'; import 'services/shake_detector.dart'; import 'services/sensors.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'; @@ -391,6 +392,8 @@ class FletCoreExtension extends FletExtension { return SemanticsServiceControl(control: control); case "Magnetometer": return MagnetometerService(control: control); + case "ScreenBrightness": + return ScreenBrightnessService(control: control); case "StoragePaths": return StoragePaths(control: control); case "Window": 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/pubspec.yaml b/packages/flet/pubspec.yaml index 7abf5501ba..c180498c01 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.2 screenshot: ^3.0.0 + screen_brightness: ^2.1.0 sensors_plus: ^7.0.0 shared_preferences: 2.5.3 shimmer: ^3.0.0 diff --git a/sdk/python/examples/controls/screen_brightness/basic.py b/sdk/python/examples/controls/screen_brightness/basic.py new file mode 100644 index 0000000000..c89585ad4f --- /dev/null +++ b/sdk/python/examples/controls/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/packages/flet/docs/controls/screenbrightness.md b/sdk/python/packages/flet/docs/controls/screenbrightness.md new file mode 100644 index 0000000000..601d935b50 --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/screenbrightness.md @@ -0,0 +1,14 @@ +--- +class_name: flet.ScreenBrightness +examples: ../../../../examples/controls/screen_brightness +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index a682c8346b..eb5814c49c 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -440,6 +440,7 @@ nav: - SelectionArea: controls/selectionarea.md - Semantics: controls/semantics.md - SemanticsService: controls/semanticsservice.md + - ScreenBrightness: controls/screenbrightness.md - ShaderMask: controls/shadermask.md - Shimmer: controls/shimmer.md - ShakeDetector: controls/shakedetector.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 60953fed39..5f12752ab4 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -428,6 +428,10 @@ from flet.controls.services.gyroscope import Gyroscope from flet.controls.services.haptic_feedback import HapticFeedback from flet.controls.services.magnetometer import Magnetometer +from flet.controls.services.screen_brightness import ( + ScreenBrightness, + ScreenBrightnessChangeEvent, +) from flet.controls.services.semantics_service import Assertiveness, SemanticsService from flet.controls.services.sensor_events import ( AccelerometerReadingEvent, @@ -892,6 +896,8 @@ "ScaleStartEvent", "ScaleUpdateEvent", "ScaleValue", + "ScreenBrightness", + "ScreenBrightnessChangeEvent", "Screenshot", "ScrollDirection", "ScrollEvent", 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..2701fa3ee1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py @@ -0,0 +1,107 @@ +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.services.service import Service +from flet.controls.types import Number + +__all__ = ["ScreenBrightness", "ScreenBrightnessChangeEvent"] + + +@dataclass +class ScreenBrightnessChangeEvent(Event["ScreenBrightness"]): + brightness: float + + +@control("ScreenBrightness") +class ScreenBrightness(Service): + """ + Provides access to control and observe system and application screen brightness. + """ + + 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}) From 36fb660431598f03a5357c4472ff5b3189ca15b6 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 12:31:48 -0800 Subject: [PATCH 09/33] Update screen brightness docs and clarify platform support Fixed example path in ScreenBrightness docs, added documentation for ScreenBrightnessChangeEvent, and updated mkdocs navigation. Clarified that accelerometer, gyroscope, and user accelerometer services are only supported on Android and iOS (not web). Expanded ScreenBrightness docstring with platform notes and Android permission instructions. --- .../flet/docs/controls/screenbrightness.md | 2 +- .../docs/types/screenbrightnesschangeevent.md | 1 + sdk/python/packages/flet/mkdocs.yml | 3 ++- .../src/flet/controls/services/accelerometer.py | 2 +- .../flet/src/flet/controls/services/gyroscope.py | 2 +- .../flet/controls/services/screen_brightness.py | 16 ++++++++++++++++ .../flet/controls/services/user_accelerometer.py | 2 +- 7 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 sdk/python/packages/flet/docs/types/screenbrightnesschangeevent.md diff --git a/sdk/python/packages/flet/docs/controls/screenbrightness.md b/sdk/python/packages/flet/docs/controls/screenbrightness.md index 601d935b50..0fb8aea85d 100644 --- a/sdk/python/packages/flet/docs/controls/screenbrightness.md +++ b/sdk/python/packages/flet/docs/controls/screenbrightness.md @@ -1,6 +1,6 @@ --- class_name: flet.ScreenBrightness -examples: ../../../../examples/controls/screen_brightness +examples: ../../examples/controls/screen_brightness --- {{ class_summary(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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index eb5814c49c..e5de138f6b 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -441,10 +441,10 @@ nav: - Semantics: controls/semantics.md - SemanticsService: controls/semanticsservice.md - ScreenBrightness: controls/screenbrightness.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 @@ -890,6 +890,7 @@ nav: - ScaleEndEvent: types/scaleendevent.md - ScaleStartEvent: types/scalestartevent.md - ScaleUpdateEvent: types/scaleupdateevent.md + - ScreenBrightnessChangeEvent: types/screenbrightnesschangeevent.md - ScrollEvent: types/scrollevent.md - TabBarHoverEvent: types/tabbarhoverevent.md - TapEvent: types/tapevent.md diff --git a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py index e3125f4818..d0c8060768 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -33,7 +33,7 @@ class Accelerometer(Service): Accelerometer reports zero acceleration if the device is free falling. Note: - * Supported platforms: Android, iOS, web. + * Supported platforms: Android, iOS. * Web ignores requested sampling intervals. """ diff --git a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py index c6c4fd9e5e..833fd9cb35 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -19,7 +19,7 @@ class Gyroscope(Service): reporting device rotation rate around each axis in `rad/s`. Note: - * Supported platforms: Android, iOS, web. + * Supported platforms: Android, iOS. * Web ignores requested sampling intervals. """ 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 index 2701fa3ee1..89b4df0ab4 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py +++ b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py @@ -12,12 +12,28 @@ @dataclass class ScreenBrightnessChangeEvent(Event["ScreenBrightness"]): 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[ 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 index b9d44651eb..8f210dd0a1 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -26,7 +26,7 @@ class UserAccelerometer(Service): from [`AccelerometerReadingEvent`][flet.]. Note: - * Supported platforms: Android, iOS, web. + * Supported platforms: Android, iOS. * Web ignores requested sampling intervals. """ From 98516c74af662a3e27d884ad62ea942c1b439577 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 13:27:19 -0800 Subject: [PATCH 10/33] Add Wakelock service to Flutter and Python SDKs Introduced a new Wakelock service to prevent device sleep in both the Flutter and Python Flet SDKs. Added the wakelock_plus dependency to the Flutter project, implemented the WakelockService in Dart, and exposed the Wakelock control in Python with documentation and an example. Updated documentation and navigation to include the new control. --- .../flet/lib/src/flet_core_extension.dart | 3 ++ packages/flet/lib/src/services/wakelock.dart | 35 +++++++++++++++++ packages/flet/pubspec.yaml | 1 + .../examples/controls/wakelock/basic.py | 39 +++++++++++++++++++ .../packages/flet/docs/controls/wakelock.md | 14 +++++++ sdk/python/packages/flet/mkdocs.yml | 1 + sdk/python/packages/flet/src/flet/__init__.py | 2 + .../src/flet/controls/services/wakelock.py | 32 +++++++++++++++ 8 files changed, 127 insertions(+) create mode 100644 packages/flet/lib/src/services/wakelock.dart create mode 100644 sdk/python/examples/controls/wakelock/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/wakelock.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/wakelock.py diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index c7ee6ab8d4..9f7a76bdcd 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -118,6 +118,7 @@ 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 'utils/cupertino_icons.dart'; import 'utils/material_icons.dart'; @@ -404,6 +405,8 @@ class FletCoreExtension extends FletExtension { 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/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/pubspec.yaml b/packages/flet/pubspec.yaml index c180498c01..d7f6555c06 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -46,6 +46,7 @@ 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: diff --git a/sdk/python/examples/controls/wakelock/basic.py b/sdk/python/examples/controls/wakelock/basic.py new file mode 100644 index 0000000000..1adf678d6b --- /dev/null +++ b/sdk/python/examples/controls/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/docs/controls/wakelock.md b/sdk/python/packages/flet/docs/controls/wakelock.md new file mode 100644 index 0000000000..6eeb2ca68c --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/wakelock.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Wakelock +examples: ../../examples/controls/wakelock +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index e5de138f6b..1f900f1a9d 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -464,6 +464,7 @@ nav: - VerticalDivider: controls/verticaldivider.md - Video: video/index.md - View: controls/view.md + - Wakelock: controls/wakelock.md - WebView: webview/index.md - WindowDragArea: controls/windowdragarea.md - CLI: diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 5f12752ab4..3078b20376 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -447,6 +447,7 @@ from flet.controls.services.storage_paths import StoragePaths from flet.controls.services.url_launcher import UrlLauncher from flet.controls.services.user_accelerometer import UserAccelerometer +from flet.controls.services.wakelock import Wakelock from flet.controls.template_route import TemplateRoute from flet.controls.text_style import ( StrutStyle, @@ -1001,6 +1002,7 @@ "View", "ViewPopEvent", "VisualDensity", + "Wakelock", "WebBrowserName", "WebDeviceInfo", "WebRenderer", 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") From ee5156c2a6108c8fef4c5e85e8e3508cef9ccd0b Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 18:29:50 -0800 Subject: [PATCH 11/33] Update package_info_plus and wakelock_plus dependencies Bump package_info_plus to version 9.0.0 in client with an override, and update wakelock_plus to 1.4.0 in both client and flet package. This ensures compatibility with the latest versions and resolves potential dependency conflicts. --- client/pubspec.lock | 10 +++++----- client/pubspec.yaml | 2 +- packages/flet/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 285bcea2cf..e9722bed8b 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -783,13 +783,13 @@ packages: source: hosted version: "1.0.0" package_info_plus: - dependency: transitive + dependency: "direct overridden" 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: @@ -1471,10 +1471,10 @@ packages: dependency: transitive 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..8c233920aa 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -81,7 +81,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/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index d7f6555c06..653116ecb8 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: web_socket_channel: ^3.0.2 window_manager: ^0.5.1 window_to_front: ^0.0.3 - wakelock_plus: ^1.2.8 + wakelock_plus: ^1.4.0 dev_dependencies: flutter_test: From cd622320eef53768956cf90409e3721e7e02c348 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 3 Dec 2025 18:58:49 -0800 Subject: [PATCH 12/33] Update wakelock_plus and add new dependencies Downgraded wakelock_plus to v1.2.8 in packages/flet and added wakelock_plus v1.4.0 and package_info_plus v9.0.0 to client dependencies. This ensures compatibility and provides additional functionality in the client package. --- client/pubspec.lock | 4 ++-- client/pubspec.yaml | 2 ++ packages/flet/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index e9722bed8b..0c104020eb 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -783,7 +783,7 @@ packages: source: hosted version: "1.0.0" package_info_plus: - dependency: "direct overridden" + dependency: "direct main" description: name: package_info_plus sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d @@ -1468,7 +1468,7 @@ packages: source: hosted version: "3.4.0" wakelock_plus: - dependency: transitive + dependency: "direct main" description: name: wakelock_plus sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 8c233920aa..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 diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 653116ecb8..d7f6555c06 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: web_socket_channel: ^3.0.2 window_manager: ^0.5.1 window_to_front: ^0.0.3 - wakelock_plus: ^1.4.0 + wakelock_plus: ^1.2.8 dev_dependencies: flutter_test: From 0bde7b8b74a8e03096d723cd2f79df5bd5a2578a Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 09:39:33 -0800 Subject: [PATCH 13/33] Add Battery service support to Flet Introduces Battery service integration in Flet, including Dart and Python implementations, documentation, and example usage. Updates plugin registrants for macOS and Windows, adds battery_plus dependency, and exposes Battery-related types and events in the Python SDK. --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + client/pubspec.lock | 24 +++++++ .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../flet/lib/src/flet_core_extension.dart | 3 + packages/flet/lib/src/services/battery.dart | 64 +++++++++++++++++++ packages/flet/pubspec.yaml | 1 + sdk/python/examples/controls/battery/basic.py | 43 +++++++++++++ .../packages/flet/docs/controls/battery.md | 14 ++++ .../packages/flet/docs/types/batterystate.md | 1 + .../docs/types/batterystatechangeevent.md | 1 + sdk/python/packages/flet/mkdocs.yml | 3 + sdk/python/packages/flet/src/flet/__init__.py | 8 +++ .../src/flet/controls/services/battery.py | 55 ++++++++++++++++ 14 files changed, 223 insertions(+) create mode 100644 packages/flet/lib/src/services/battery.dart create mode 100644 sdk/python/examples/controls/battery/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/battery.md create mode 100644 sdk/python/packages/flet/docs/types/batterystate.md create mode 100644 sdk/python/packages/flet/docs/types/batterystatechangeevent.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/battery.py diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 1ef11de80d..fed1eb07d7 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import audioplayers_darwin +import battery_plus import device_info_plus import file_picker import geolocator_apple @@ -27,6 +28,7 @@ import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) diff --git a/client/pubspec.lock b/client/pubspec.lock index 0c104020eb..266013709c 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: "03d5a6bb36db9d2b977c548f6b0262d5a84c4d5a4cfee2edac4a91d57011b365" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + 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: @@ -1339,6 +1355,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: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 4e4e19f6ce..0bda6b89da 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -23,6 +24,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + BatteryPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 8a2e3c09da..fd8264c5f3 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows + battery_plus geolocator_windows media_kit_libs_windows_video media_kit_video diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index 9f7a76bdcd..d9b48a1ebf 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -107,6 +107,7 @@ import 'flet_extension.dart'; import 'flet_service.dart'; import 'models/control.dart'; import 'services/browser_context_menu.dart'; +import 'services/battery.dart'; import 'services/clipboard.dart'; import 'services/file_picker.dart'; import 'services/haptic_feedback.dart'; @@ -377,6 +378,8 @@ class FletCoreExtension extends FletExtension { return AccelerometerService(control: control); case "Barometer": return BarometerService(control: control); + case "Battery": + return BatteryService(control: control); case "Clipboard": return ClipboardService(control: control); case "FilePicker": 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/pubspec.yaml b/packages/flet/pubspec.yaml index d7f6555c06..4f1c5e21e7 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter + battery_plus: ^6.2.2 collection: ^1.19.0 device_info_plus: ^12.1.0 equatable: ^2.0.3 diff --git a/sdk/python/examples/controls/battery/basic.py b/sdk/python/examples/controls/battery/basic.py new file mode 100644 index 0000000000..9b7e83a414 --- /dev/null +++ b/sdk/python/examples/controls/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/packages/flet/docs/controls/battery.md b/sdk/python/packages/flet/docs/controls/battery.md new file mode 100644 index 0000000000..e62a535df9 --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/battery.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Battery +examples: ../../examples/controls/battery +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 1f900f1a9d..07e7278387 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -275,6 +275,7 @@ nav: - Badge: types/badge.md - Banner: controls/banner.md - Barometer: controls/barometer.md + - Battery: controls/battery.md - BottomAppBar: controls/bottomappbar.md - BottomSheet: controls/bottomsheet.md - Button: controls/button.md @@ -852,6 +853,8 @@ nav: - AppLifecycleStateChangeEvent: types/applifecyclestatechangeevent.md - AutoCompleteSelectEvent: types/autocompleteselectevent.md - BarometerReadingEvent: types/barometerreadingevent.md + - BatteryState: types/batterystate.md + - BatteryStateChangeEvent: types/batterystatechangeevent.md - CanvasResizeEvent: types/canvasresizeevent.md - ContextMenuDismissEvent: types/contextmenudismissevent.md - ContextMenuSelectEvent: types/contextmenuselectevent.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 3078b20376..d75b7c95a3 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -416,6 +416,11 @@ ) from flet.controls.services.accelerometer import Accelerometer from flet.controls.services.barometer import Barometer +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.file_picker import ( @@ -593,6 +598,9 @@ "BarometerReadingEvent", "BaseControl", "BasePage", + "Battery", + "BatteryState", + "BatteryStateChangeEvent", "BeveledRectangleBorder", "BlendMode", "Blur", 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..d87341ee37 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/battery.py @@ -0,0 +1,55 @@ +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): + CHARGING = "charging" + CONNECTED_NOT_CHARGING = "connectedNotCharging" + DISCHARGING = "discharging" + FULL = "full" + UNKNOWN = "unknown" + + +@dataclass +class BatteryStateChangeEvent(Event["Battery"]): + state: BatteryState + + +@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") From 14a68efb8c8a1778581af03a89b6078703c542b8 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 10:04:40 -0800 Subject: [PATCH 14/33] Add connectivity service and documentation Introduced Connectivity service in both Dart and Python SDKs, enabling connectivity status checks and change notifications. Registered connectivity_plus plugin for macOS and Windows clients, updated dependencies, and added example usage and documentation for the new service. --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + client/pubspec.lock | 24 +++++++ .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../flet/lib/src/flet_core_extension.dart | 3 + .../flet/lib/src/services/connectivity.dart | 63 +++++++++++++++++++ packages/flet/pubspec.yaml | 1 + .../examples/controls/connectivity/basic.py | 35 +++++++++++ .../flet/docs/controls/connectivity.md | 14 +++++ .../docs/types/connectivitychangeevent.md | 1 + .../flet/docs/types/connectivityresult.md | 1 + sdk/python/packages/flet/mkdocs.yml | 3 + sdk/python/packages/flet/src/flet/__init__.py | 8 +++ .../src/flet/controls/services/battery.py | 7 +++ .../flet/controls/services/connectivity.py | 49 +++++++++++++++ .../controls/services/screen_brightness.py | 4 ++ 16 files changed, 219 insertions(+) create mode 100644 packages/flet/lib/src/services/connectivity.dart create mode 100644 sdk/python/examples/controls/connectivity/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/connectivity.md create mode 100644 sdk/python/packages/flet/docs/types/connectivitychangeevent.md create mode 100644 sdk/python/packages/flet/docs/types/connectivityresult.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/connectivity.py diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index fed1eb07d7..3c6172d1d7 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import audioplayers_darwin import battery_plus +import connectivity_plus import device_info_plus import file_picker import geolocator_apple @@ -29,6 +30,7 @@ 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")) diff --git a/client/pubspec.lock b/client/pubspec.lock index 266013709c..41f35743ac 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -145,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + 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: @@ -798,6 +814,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + 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: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 0bda6b89da..1a9c7b103d 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); BatteryPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index fd8264c5f3..e6895fcf40 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows battery_plus + connectivity_plus geolocator_windows media_kit_libs_windows_video media_kit_video diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index d9b48a1ebf..678c6cb438 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -109,6 +109,7 @@ import 'models/control.dart'; import 'services/browser_context_menu.dart'; import 'services/battery.dart'; import 'services/clipboard.dart'; +import 'services/connectivity.dart'; import 'services/file_picker.dart'; import 'services/haptic_feedback.dart'; import 'services/semantics_service.dart'; @@ -382,6 +383,8 @@ class FletCoreExtension extends FletExtension { return BatteryService(control: control); case "Clipboard": return ClipboardService(control: control); + case "Connectivity": + return ConnectivityService(control: control); case "FilePicker": return FilePickerService(control: control); case "HapticFeedback": diff --git a/packages/flet/lib/src/services/connectivity.dart b/packages/flet/lib/src/services/connectivity.dart new file mode 100644 index 0000000000..7085b5f466 --- /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 "check_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_connectivity_change") == true; + if (listenChange && _subscription == null) { + _subscription = _connectivity.onConnectivityChanged.listen( + (List result) { + control.triggerEvent("connectivity_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/pubspec.yaml b/packages/flet/pubspec.yaml index 4f1c5e21e7..9c38934f94 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: sdk: flutter battery_plus: ^6.2.2 collection: ^1.19.0 + connectivity_plus: ^6.1.2 device_info_plus: ^12.1.0 equatable: ^2.0.3 file_picker: ^10.3.3 diff --git a/sdk/python/examples/controls/connectivity/basic.py b/sdk/python/examples/controls/connectivity/basic.py new file mode 100644 index 0000000000..24f4bb062a --- /dev/null +++ b/sdk/python/examples/controls/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.check_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_connectivity_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/packages/flet/docs/controls/connectivity.md b/sdk/python/packages/flet/docs/controls/connectivity.md new file mode 100644 index 0000000000..4407fad1ed --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/connectivity.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Connectivity +examples: ../../examples/controls/connectivity +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} 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/connectivityresult.md b/sdk/python/packages/flet/docs/types/connectivityresult.md new file mode 100644 index 0000000000..2b48530584 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/connectivityresult.md @@ -0,0 +1 @@ +{{ class_all_options("flet.ConnectivityResult", separate_signature=False) }} diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 07e7278387..3b3c732258 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -276,6 +276,7 @@ nav: - Banner: controls/banner.md - Barometer: controls/barometer.md - Battery: controls/battery.md + - Connectivity: controls/connectivity.md - BottomAppBar: controls/bottomappbar.md - BottomSheet: controls/bottomsheet.md - Button: controls/button.md @@ -855,6 +856,8 @@ nav: - BarometerReadingEvent: types/barometerreadingevent.md - BatteryState: types/batterystate.md - BatteryStateChangeEvent: types/batterystatechangeevent.md + - ConnectivityResult: types/connectivityresult.md + - ConnectivityChangeEvent: types/connectivitychangeevent.md - CanvasResizeEvent: types/canvasresizeevent.md - ContextMenuDismissEvent: types/contextmenudismissevent.md - ContextMenuSelectEvent: types/contextmenuselectevent.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index d75b7c95a3..386fb2b661 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -423,6 +423,11 @@ ) from flet.controls.services.browser_context_menu import BrowserContextMenu from flet.controls.services.clipboard import Clipboard +from flet.controls.services.connectivity import ( + Connectivity, + ConnectivityChangeEvent, + ConnectivityResult, +) from flet.controls.services.file_picker import ( FilePicker, FilePickerFile, @@ -647,6 +652,9 @@ "Colors", "Column", "Component", + "Connectivity", + "ConnectivityChangeEvent", + "ConnectivityResult", "ConstrainedControl", "Container", "Context", diff --git a/sdk/python/packages/flet/src/flet/controls/services/battery.py b/sdk/python/packages/flet/src/flet/controls/services/battery.py index d87341ee37..d7938ef2a9 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/battery.py +++ b/sdk/python/packages/flet/src/flet/controls/services/battery.py @@ -19,7 +19,14 @@ class BatteryState(Enum): @dataclass class BatteryStateChangeEvent(Event["Battery"]): + """ + Event fired when battery state changes. + """ + state: BatteryState + """ + Current battery state. + """ @control("Battery") 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..0a3e45f780 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py @@ -0,0 +1,49 @@ +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", "ConnectivityResult"] + + +class ConnectivityResult(Enum): + BLUETOOTH = "bluetooth" + ETHERNET = "ethernet" + MOBILE = "mobile" + NONE = "none" + OTHER = "other" + VPN = "vpn" + WIFI = "wifi" + + +@dataclass +class ConnectivityChangeEvent(Event["Connectivity"]): + """Event fired when connectivity changes.""" + + connectivity: list[ConnectivityResult] + """ + Current connectivity state(s). + """ + + +@control("Connectivity") +class Connectivity(Service): + """ + Provides connectivity status and change notifications. + """ + + on_connectivity_change: Optional[EventHandler[ConnectivityChangeEvent]] = None + """ + Called when connectivity changes. + """ + + async def check_connectivity(self) -> list[ConnectivityResult]: + """ + Returns the current connectivity state(s). + """ + + result = await self._invoke_method("check_connectivity") + return [ConnectivityResult(r) for r in result] 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 index 89b4df0ab4..873c94a071 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py +++ b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py @@ -11,6 +11,10 @@ @dataclass class ScreenBrightnessChangeEvent(Event["ScreenBrightness"]): + """ + Event fired when screen brightness changes. + """ + brightness: float """ The new screen brightness, in range `0.0..1.0`. From f725b1d3b772659f3a7b3d7719847462a8740a0d Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 11:55:02 -0800 Subject: [PATCH 15/33] Add and improve docstrings for service controls Added and enhanced docstrings for classes and methods in battery, browser_context_menu, clipboard, connectivity, service, shared_preferences, storage_paths, and url_launcher modules to improve code documentation and clarity for developers. --- .../src/flet/controls/services/battery.py | 19 +++++++++++++ .../controls/services/browser_context_menu.py | 23 ++++++++++++++++ .../src/flet/controls/services/clipboard.py | 24 +++++------------ .../flet/controls/services/connectivity.py | 27 ++++++++++++++++++- .../src/flet/controls/services/service.py | 4 +++ .../controls/services/shared_preferences.py | 22 +++++++++++++++ .../flet/controls/services/storage_paths.py | 2 ++ .../flet/controls/services/url_launcher.py | 4 +++ 8 files changed, 106 insertions(+), 19 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/controls/services/battery.py b/sdk/python/packages/flet/src/flet/controls/services/battery.py index d7938ef2a9..4ed27a17e4 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/battery.py +++ b/sdk/python/packages/flet/src/flet/controls/services/battery.py @@ -10,11 +10,30 @@ 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 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 index 0a3e45f780..8ece8d96b5 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/connectivity.py +++ b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py @@ -10,13 +10,38 @@ class ConnectivityResult(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 @@ -32,7 +57,7 @@ class ConnectivityChangeEvent(Event["Connectivity"]): @control("Connectivity") class Connectivity(Service): """ - Provides connectivity status and change notifications. + Provides device connectivity status and change notifications. """ on_connectivity_change: Optional[EventHandler[ConnectivityChangeEvent]] = None 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..3a30c13d8b 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/service.py +++ b/sdk/python/packages/flet/src/flet/controls/services/service.py @@ -9,6 +9,10 @@ @dataclass(kw_only=True) class Service(BaseControl): + """ + Base class for user services. + """ + def init(self): super().init() with contextlib.suppress(RuntimeError): 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..9be472b733 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 @@ -9,6 +9,10 @@ @control("UrlLauncher") class UrlLauncher(Service): + """ + Provides access to URL launching capabilities. + """ + async def launch_url( self, url: Union[str, Url], From 6b42bb68faaca5b84eaf1d11bede2fc21f0b238d Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 13:54:23 -0800 Subject: [PATCH 16/33] Add Share service and Python API for sharing Introduces ShareService in Dart and integrates share_plus for sharing text, links, and files. Adds Python API (Share, ShareFile, ShareResult, etc.), documentation, and example usage. Updates plugin registration and dependencies for macOS and Windows. --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + client/pubspec.lock | 24 +++ .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../flet/lib/src/flet_core_extension.dart | 3 + packages/flet/lib/src/services/share.dart | 189 +++++++++++++++++ packages/flet/pubspec.yaml | 1 + sdk/python/examples/controls/share/basic.py | 57 +++++ .../packages/flet/docs/controls/share.md | 14 ++ .../flet/docs/types/cupertinoactivitytype.md | 1 + .../packages/flet/docs/types/sharefile.md | 1 + .../packages/flet/docs/types/shareresult.md | 1 + .../flet/docs/types/shareresultstatus.md | 1 + sdk/python/packages/flet/mkdocs.yml | 5 + sdk/python/packages/flet/src/flet/__init__.py | 12 ++ .../flet/src/flet/controls/services/share.py | 199 ++++++++++++++++++ 16 files changed, 514 insertions(+) create mode 100644 packages/flet/lib/src/services/share.dart create mode 100644 sdk/python/examples/controls/share/basic.py create mode 100644 sdk/python/packages/flet/docs/controls/share.md create mode 100644 sdk/python/packages/flet/docs/types/cupertinoactivitytype.md create mode 100644 sdk/python/packages/flet/docs/types/sharefile.md create mode 100644 sdk/python/packages/flet/docs/types/shareresult.md create mode 100644 sdk/python/packages/flet/docs/types/shareresultstatus.md create mode 100644 sdk/python/packages/flet/src/flet/controls/services/share.py diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index 3c6172d1d7..01c0d4e94d 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -19,6 +19,7 @@ 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 @@ -42,6 +43,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 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 41f35743ac..4356782da1 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -798,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: @@ -1214,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: diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 1a9c7b103d..1fc0608790 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { 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 e6895fcf40..ce206ef9ef 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST rive_common screen_brightness_windows screen_retriever_windows + share_plus url_launcher_windows volume_controller window_manager diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index 678c6cb438..b34ba7bba6 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -112,6 +112,7 @@ import 'services/clipboard.dart'; import 'services/connectivity.dart'; import 'services/file_picker.dart'; import 'services/haptic_feedback.dart'; +import 'services/share.dart'; import 'services/semantics_service.dart'; import 'services/shake_detector.dart'; import 'services/sensors.dart'; @@ -385,6 +386,8 @@ class FletCoreExtension extends FletExtension { return ClipboardService(control: control); case "Connectivity": return ConnectivityService(control: control); + case "Share": + return ShareService(control: control); case "FilePicker": return FilePickerService(control: control); case "HapticFeedback": 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/pubspec.yaml b/packages/flet/pubspec.yaml index 9c38934f94..2829a50d2d 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: screen_brightness: ^2.1.0 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 diff --git a/sdk/python/examples/controls/share/basic.py b/sdk/python/examples/controls/share/basic.py new file mode 100644 index 0000000000..02e55b64d6 --- /dev/null +++ b/sdk/python/examples/controls/share/basic.py @@ -0,0 +1,57 @@ +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(): + 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}" + + 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", on_click=do_share_files), + ], + wrap=True, + ), + status, + result_raw, + ], + ) + ) + ) + + +ft.run(main) diff --git a/sdk/python/packages/flet/docs/controls/share.md b/sdk/python/packages/flet/docs/controls/share.md new file mode 100644 index 0000000000..d727b0077b --- /dev/null +++ b/sdk/python/packages/flet/docs/controls/share.md @@ -0,0 +1,14 @@ +--- +class_name: flet.Share +examples: ../../examples/controls/share +--- + +{{ class_summary(class_name) }} + +## Examples + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md b/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md new file mode 100644 index 0000000000..c637947ed5 --- /dev/null +++ b/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md @@ -0,0 +1 @@ +{{ class_all_options("flet.CupertinoActivityType", 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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 3b3c732258..82a9dc6b02 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -443,6 +443,7 @@ nav: - Semantics: controls/semantics.md - SemanticsService: controls/semanticsservice.md - ScreenBrightness: controls/screenbrightness.md + - Share: controls/share.md - Screenshot: controls/screenshot.md - ShaderMask: controls/shadermask.md - Shimmer: controls/shimmer.md @@ -898,6 +899,10 @@ nav: - ScaleStartEvent: types/scalestartevent.md - ScaleUpdateEvent: types/scaleupdateevent.md - ScreenBrightnessChangeEvent: types/screenbrightnesschangeevent.md + - ShareResult: types/shareresult.md + - ShareResultStatus: types/shareresultstatus.md + - ShareFile: types/sharefile.md + - CupertinoActivityType: types/cupertinoactivitytype.md - ScrollEvent: types/scrollevent.md - TabBarHoverEvent: types/tabbarhoverevent.md - TapEvent: types/tapevent.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 386fb2b661..166279fb1e 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -453,6 +453,13 @@ ) from flet.controls.services.service import Service from flet.controls.services.shake_detector import ShakeDetector +from flet.controls.services.share import ( + CupertinoActivityType, + Share, + 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 @@ -672,6 +679,7 @@ "CupertinoActionSheet", "CupertinoActionSheetAction", "CupertinoActivityIndicator", + "CupertinoActivityType", "CupertinoAlertDialog", "CupertinoAppBar", "CupertinoBottomSheet", @@ -937,6 +945,10 @@ "ShaderMask", "ShakeDetector", "ShapeBorder", + "Share", + "ShareFile", + "ShareResult", + "ShareResultStatus", "SharedPreferences", "Shimmer", "ShimmerDirection", 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..00d29d175d --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/share.py @@ -0,0 +1,199 @@ +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__ = [ + "CupertinoActivityType", + "Share", + "ShareFile", + "ShareResult", + "ShareResultStatus", +] + + +class ShareResultStatus(Enum): + SUCCESS = "success" + DISMISSED = "dismissed" + UNAVAILABLE = "unavailable" + + +class CupertinoActivityType(Enum): + POST_TO_FACEBOOK = "postToFacebook" + POST_TO_TWITTER = "postToTwitter" + POST_TO_WEIBO = "postToWeibo" + MESSAGE = "message" + MAIL = "mail" + PRINT = "print" + COPY_TO_PASTEBOARD = "copyToPasteboard" + ASSIGN_TO_CONTACT = "assignToContact" + SAVE_TO_CAMERA_ROLL = "saveToCameraRoll" + ADD_TO_READING_LIST = "addToReadingList" + POST_TO_FLICKR = "postToFlickr" + POST_TO_VIMEO = "postToVimeo" + POST_TO_TENCENT_WEIBO = "postToTencentWeibo" + AIR_DROP = "airDrop" + OPEN_IN_IBOOKS = "openInIBooks" + MARKUP_AS_PDF = "markupAsPDF" + SHARE_PLAY = "sharePlay" + COLLABORATION_INVITE_WITH_LINK = "collaborationInviteWithLink" + COLLABORATION_COPY_LINK = "collaborationCopyLink" + ADD_TO_HOME_SCREEN = "addToHomeScreen" + + +@dataclass +class ShareResult: + status: ShareResultStatus + raw: str + + +@dataclass +class ShareFile: + path: Optional[str] = None + data: Optional[bytes] = None + mime_type: Optional[str] = None + name: Optional[str] = None + + 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": + return ShareFile(path=path, name=name) + + @staticmethod + def from_bytes( + data: bytes, *, mime_type: Optional[str] = None, name: Optional[str] = None + ) -> "ShareFile": + return ShareFile(data=data, mime_type=mime_type, name=name) + + +@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[CupertinoActivityType]] = None, + ) -> ShareResult: + 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[CupertinoActivityType]] = None, + ) -> ShareResult: + 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[CupertinoActivityType]] = None, + ) -> ShareResult: + 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[CupertinoActivityType]] = 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 From 308f3c6348b904688b360813ec5edc2c0eedc94c Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 14:25:20 -0800 Subject: [PATCH 17/33] Rename CupertinoActivityType to ShareCupertinoActivityType Replaces the CupertinoActivityType enum with ShareCupertinoActivityType throughout the codebase for clarity and consistency. Updates documentation, mkdocs navigation, and references in __init__.py and share.py. Adds docstrings and improves type annotations for share-related classes and methods. --- .../flet/docs/types/cupertinoactivitytype.md | 1 - .../docs/types/sharecupertinoactivitytype.md | 1 + .../packages/flet/docs/types/sharefile.md | 1 + sdk/python/packages/flet/mkdocs.yml | 2 +- sdk/python/packages/flet/src/flet/__init__.py | 4 +- .../flet/src/flet/controls/services/share.py | 138 +++++++++++++++++- 6 files changed, 137 insertions(+), 10 deletions(-) delete mode 100644 sdk/python/packages/flet/docs/types/cupertinoactivitytype.md create mode 100644 sdk/python/packages/flet/docs/types/sharecupertinoactivitytype.md diff --git a/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md b/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md deleted file mode 100644 index c637947ed5..0000000000 --- a/sdk/python/packages/flet/docs/types/cupertinoactivitytype.md +++ /dev/null @@ -1 +0,0 @@ -{{ class_all_options("flet.CupertinoActivityType", separate_signature=False) }} 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 index d396822daf..14ac050138 100644 --- a/sdk/python/packages/flet/docs/types/sharefile.md +++ b/sdk/python/packages/flet/docs/types/sharefile.md @@ -1 +1,2 @@ {{ class_all_options("flet.ShareFile", separate_signature=False) }} +{{ class_all_options("flet.ShareFile", separate_signature=False) }} diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 82a9dc6b02..d26ad154a7 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -902,7 +902,7 @@ nav: - ShareResult: types/shareresult.md - ShareResultStatus: types/shareresultstatus.md - ShareFile: types/sharefile.md - - CupertinoActivityType: types/cupertinoactivitytype.md + - ShareCupertinoActivityType: types/sharecupertinoactivitytype.md - ScrollEvent: types/scrollevent.md - TabBarHoverEvent: types/tabbarhoverevent.md - TapEvent: types/tapevent.md diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 166279fb1e..047774da8c 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -454,8 +454,8 @@ from flet.controls.services.service import Service from flet.controls.services.shake_detector import ShakeDetector from flet.controls.services.share import ( - CupertinoActivityType, Share, + ShareCupertinoActivityType, ShareFile, ShareResult, ShareResultStatus, @@ -679,7 +679,6 @@ "CupertinoActionSheet", "CupertinoActionSheetAction", "CupertinoActivityIndicator", - "CupertinoActivityType", "CupertinoAlertDialog", "CupertinoAppBar", "CupertinoBottomSheet", @@ -946,6 +945,7 @@ "ShakeDetector", "ShapeBorder", "Share", + "ShareCupertinoActivityType", "ShareFile", "ShareResult", "ShareResultStatus", diff --git a/sdk/python/packages/flet/src/flet/controls/services/share.py b/sdk/python/packages/flet/src/flet/controls/services/share.py index 00d29d175d..60c39ba7d0 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/share.py +++ b/sdk/python/packages/flet/src/flet/controls/services/share.py @@ -9,8 +9,8 @@ from flet.utils.from_dict import from_dict __all__ = [ - "CupertinoActivityType", "Share", + "ShareCupertinoActivityType", "ShareFile", "ShareResult", "ShareResultStatus", @@ -18,46 +18,107 @@ 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 CupertinoActivityType(Enum): +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: @@ -65,15 +126,31 @@ def __post_init__(self): @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): """ @@ -90,8 +167,25 @@ async def share_text( share_position_origin: Optional[Offset] = None, download_fallback_enabled: bool = True, mail_to_fallback_enabled: bool = True, - excluded_cupertino_activities: Optional[Iterable[CupertinoActivityType]] = None, + 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( @@ -112,8 +206,20 @@ async def share_uri( uri: str, *, share_position_origin: Optional[Offset] = None, - excluded_cupertino_activities: Optional[Iterable[CupertinoActivityType]] = 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( @@ -135,8 +241,26 @@ async def share_files( share_position_origin: Optional[Offset] = None, download_fallback_enabled: bool = True, mail_to_fallback_enabled: bool = True, - excluded_cupertino_activities: Optional[Iterable[CupertinoActivityType]] = None, + 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.") @@ -168,7 +292,9 @@ def _share_args( share_position_origin: Optional[Offset] = None, download_fallback_enabled: bool = True, mail_to_fallback_enabled: bool = True, - excluded_cupertino_activities: Optional[Iterable[CupertinoActivityType]] = None, + excluded_cupertino_activities: Optional[ + Iterable[ShareCupertinoActivityType] + ] = None, ): args: dict = {} if text is not None: From 4168e0c15107cc248a8877882cff13ab4ee2d839 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 17:08:37 -0800 Subject: [PATCH 18/33] Refactor docs: move service-related files and update navigation Service-related documentation files were moved from 'controls' and 'types' to a new 'services' directory. The mkdocs navigation was updated to reflect this change, grouping all service docs under a new 'Services' section. Macros and overview rendering were refactored to support both controls and services navigation. Minor cleanup in page.dart and macro Python files. --- packages/flet/lib/src/controls/page.dart | 5 +- .../flet/docs/extras/macros/__init__.py | 8 ++- .../docs/extras/macros/controls_overview.py | 8 +-- .../{controls => services}/accelerometer.md | 0 .../docs/{controls => services}/barometer.md | 0 .../docs/{controls => services}/battery.md | 0 .../{types => services}/browsercontextmenu.md | 0 .../docs/{controls => services}/clipboard.md | 0 .../{controls => services}/connectivity.md | 0 .../docs/{controls => services}/filepicker.md | 0 .../docs/{controls => services}/gyroscope.md | 0 .../{controls => services}/hapticfeedback.md | 0 .../packages/flet/docs/services/index.md | 5 ++ .../{controls => services}/magnetometer.md | 0 .../screenbrightness.md | 0 .../semanticsservice.md | 0 .../{controls => services}/shakedetector.md | 0 .../flet/docs/{controls => services}/share.md | 0 .../sharedpreferences.md | 0 .../{controls => services}/storagepaths.md | 0 .../{controls => services}/urllauncher.md | 0 .../useraccelerometer.md | 0 .../docs/{controls => services}/wakelock.md | 0 sdk/python/packages/flet/mkdocs.yml | 50 ++++++++++--------- 24 files changed, 42 insertions(+), 34 deletions(-) rename sdk/python/packages/flet/docs/{controls => services}/accelerometer.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/barometer.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/battery.md (100%) rename sdk/python/packages/flet/docs/{types => services}/browsercontextmenu.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/clipboard.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/connectivity.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/filepicker.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/gyroscope.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/hapticfeedback.md (100%) create mode 100644 sdk/python/packages/flet/docs/services/index.md rename sdk/python/packages/flet/docs/{controls => services}/magnetometer.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/screenbrightness.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/semanticsservice.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/shakedetector.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/share.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/sharedpreferences.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/storagepaths.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/urllauncher.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/useraccelerometer.md (100%) rename sdk/python/packages/flet/docs/{controls => services}/wakelock.md (100%) diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index 3bac74ab36..1cc8f65d78 100644 --- a/packages/flet/lib/src/controls/page.dart +++ b/packages/flet/lib/src/controls/page.dart @@ -67,7 +67,6 @@ class _PageControlState extends State with WidgetsBindingObserver { final Map _multiViews = {}; bool _registeredFromMultiViews = false; - List? _appliedDeviceOrientations; @override void initState() { @@ -154,9 +153,7 @@ class _PageControlState extends State with WidgetsBindingObserver { var backend = FletBackend.of(context); _pageServices ??= ServiceRegistry( - control: widget.control, - propertyName: "_services", - backend: backend); + control: widget.control, propertyName: "_services", backend: backend); var userServicesControl = widget.control.child("_user_services"); if (userServicesControl != null) { diff --git a/sdk/python/packages/flet/docs/extras/macros/__init__.py b/sdk/python/packages/flet/docs/extras/macros/__init__.py index 9c84997353..4c9be0f4c6 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_sub_nav_overview def define_env(env): @@ -129,4 +129,8 @@ 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") 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..0115a25047 100644 --- a/sdk/python/packages/flet/docs/extras/macros/controls_overview.py +++ b/sdk/python/packages/flet/docs/extras/macros/controls_overview.py @@ -102,7 +102,7 @@ def _format_nav_list(nodes: list[dict[str, Any]], depth: int = 0) -> list[str]: return lines -def render_controls_overview() -> str: +def render_sub_nav_overview(nav_name: str) -> str: """Produce the controls overview section from mkdocs.yml.""" from mkdocs.config import load_config @@ -111,8 +111,8 @@ def render_controls_overview() -> str: 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 [] + api_section = _find_nav_branch(api_reference, nav_name) if api_reference else None + nodes = _build_nav_nodes(api_section) if isinstance(api_section, list) else [] if not nodes: return "" lines = _format_nav_list(nodes) @@ -120,4 +120,4 @@ def render_controls_overview() -> str: if __name__ == "__main__": - print(render_controls_overview()) + print(render_sub_nav_overview("Controls")) diff --git a/sdk/python/packages/flet/docs/controls/accelerometer.md b/sdk/python/packages/flet/docs/services/accelerometer.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/accelerometer.md rename to sdk/python/packages/flet/docs/services/accelerometer.md diff --git a/sdk/python/packages/flet/docs/controls/barometer.md b/sdk/python/packages/flet/docs/services/barometer.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/barometer.md rename to sdk/python/packages/flet/docs/services/barometer.md diff --git a/sdk/python/packages/flet/docs/controls/battery.md b/sdk/python/packages/flet/docs/services/battery.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/battery.md rename to sdk/python/packages/flet/docs/services/battery.md diff --git a/sdk/python/packages/flet/docs/types/browsercontextmenu.md b/sdk/python/packages/flet/docs/services/browsercontextmenu.md similarity index 100% rename from sdk/python/packages/flet/docs/types/browsercontextmenu.md rename to sdk/python/packages/flet/docs/services/browsercontextmenu.md diff --git a/sdk/python/packages/flet/docs/controls/clipboard.md b/sdk/python/packages/flet/docs/services/clipboard.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/clipboard.md rename to sdk/python/packages/flet/docs/services/clipboard.md diff --git a/sdk/python/packages/flet/docs/controls/connectivity.md b/sdk/python/packages/flet/docs/services/connectivity.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/connectivity.md rename to sdk/python/packages/flet/docs/services/connectivity.md diff --git a/sdk/python/packages/flet/docs/controls/filepicker.md b/sdk/python/packages/flet/docs/services/filepicker.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/filepicker.md rename to sdk/python/packages/flet/docs/services/filepicker.md diff --git a/sdk/python/packages/flet/docs/controls/gyroscope.md b/sdk/python/packages/flet/docs/services/gyroscope.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/gyroscope.md rename to sdk/python/packages/flet/docs/services/gyroscope.md diff --git a/sdk/python/packages/flet/docs/controls/hapticfeedback.md b/sdk/python/packages/flet/docs/services/hapticfeedback.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/hapticfeedback.md rename to sdk/python/packages/flet/docs/services/hapticfeedback.md 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/controls/magnetometer.md b/sdk/python/packages/flet/docs/services/magnetometer.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/magnetometer.md rename to sdk/python/packages/flet/docs/services/magnetometer.md diff --git a/sdk/python/packages/flet/docs/controls/screenbrightness.md b/sdk/python/packages/flet/docs/services/screenbrightness.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/screenbrightness.md rename to sdk/python/packages/flet/docs/services/screenbrightness.md diff --git a/sdk/python/packages/flet/docs/controls/semanticsservice.md b/sdk/python/packages/flet/docs/services/semanticsservice.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/semanticsservice.md rename to sdk/python/packages/flet/docs/services/semanticsservice.md diff --git a/sdk/python/packages/flet/docs/controls/shakedetector.md b/sdk/python/packages/flet/docs/services/shakedetector.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/shakedetector.md rename to sdk/python/packages/flet/docs/services/shakedetector.md diff --git a/sdk/python/packages/flet/docs/controls/share.md b/sdk/python/packages/flet/docs/services/share.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/share.md rename to sdk/python/packages/flet/docs/services/share.md diff --git a/sdk/python/packages/flet/docs/controls/sharedpreferences.md b/sdk/python/packages/flet/docs/services/sharedpreferences.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/sharedpreferences.md rename to sdk/python/packages/flet/docs/services/sharedpreferences.md diff --git a/sdk/python/packages/flet/docs/controls/storagepaths.md b/sdk/python/packages/flet/docs/services/storagepaths.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/storagepaths.md rename to sdk/python/packages/flet/docs/services/storagepaths.md diff --git a/sdk/python/packages/flet/docs/controls/urllauncher.md b/sdk/python/packages/flet/docs/services/urllauncher.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/urllauncher.md rename to sdk/python/packages/flet/docs/services/urllauncher.md diff --git a/sdk/python/packages/flet/docs/controls/useraccelerometer.md b/sdk/python/packages/flet/docs/services/useraccelerometer.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/useraccelerometer.md rename to sdk/python/packages/flet/docs/services/useraccelerometer.md diff --git a/sdk/python/packages/flet/docs/controls/wakelock.md b/sdk/python/packages/flet/docs/services/wakelock.md similarity index 100% rename from sdk/python/packages/flet/docs/controls/wakelock.md rename to sdk/python/packages/flet/docs/services/wakelock.md diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index d26ad154a7..4b40e80170 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -264,9 +264,6 @@ nav: - BaseAd: ads/basead.md - InterstitialAd: ads/interstitialad.md # - NativeAd: ads/nativead.md - - Audio: audio/index.md - - AudioRecorder: audio_recorder/index.md - - Accelerometer: controls/accelerometer.md - AlertDialog: controls/alertdialog.md - AnimatedSwitcher: controls/animatedswitcher.md - AppBar: controls/appbar.md @@ -274,9 +271,6 @@ nav: - AutofillGroup: controls/autofillgroup.md - Badge: types/badge.md - Banner: controls/banner.md - - Barometer: controls/barometer.md - - Battery: controls/battery.md - - Connectivity: controls/connectivity.md - BottomAppBar: controls/bottomappbar.md - BottomSheet: controls/bottomsheet.md - Button: controls/button.md @@ -360,19 +354,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 - - Gyroscope: controls/gyroscope.md - - HapticFeedback: controls/hapticfeedback.md - Icon: controls/icon.md - IconButton: controls/iconbutton.md - Image: controls/image.md @@ -403,7 +392,6 @@ nav: - TextSourceAttribution: map/text_source_attribution.md - ImageSourceAttribution: map/image_source_attribution.md - Markdown: controls/markdown.md - - Magnetometer: controls/magnetometer.md - MenuBar: controls/menubar.md - MenuItemButton: controls/menuitembutton.md - MergeSemantics: controls/mergesemantics.md @@ -423,7 +411,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 @@ -441,13 +428,9 @@ nav: - Segment: controls/segment.md - SelectionArea: controls/selectionarea.md - Semantics: controls/semantics.md - - SemanticsService: controls/semanticsservice.md - - ScreenBrightness: controls/screenbrightness.md - - Share: controls/share.md - Screenshot: controls/screenshot.md - ShaderMask: controls/shadermask.md - Shimmer: controls/shimmer.md - - ShakeDetector: controls/shakedetector.md - Slider: controls/slider.md - SnackBar: controls/snackbar.md - Stack: controls/stack.md @@ -463,13 +446,37 @@ nav: - TextField: controls/textfield.md - TimePicker: controls/timepicker.md - TransparentPointer: controls/transparentpointer.md - - UserAccelerometer: controls/useraccelerometer.md - VerticalDivider: controls/verticaldivider.md - Video: video/index.md - View: controls/view.md - - Wakelock: controls/wakelock.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 @@ -526,7 +533,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 @@ -568,7 +574,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 @@ -681,9 +686,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 @@ -739,7 +742,6 @@ nav: - TooltipTheme: types/tooltiptheme.md - Tooltip: types/tooltip.md - Url: types/url.md - - UrlLauncher: controls/urllauncher.md - UnderlineTabIndicator: types/underlinetabindicator.md - Video: - PlaylistMode: video/types/playlist_mode.md From 24965d38e7ace969b8f4d429181d62f30e95386f Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 4 Dec 2025 17:32:08 -0800 Subject: [PATCH 19/33] Add API Reference and Types docs, update nav structure Introduces new API Reference and Types index documentation pages. Updates mkdocs.yml to include these sections in the navigation, providing better structure and discoverability for API and type documentation. Minor wording improvements in the cookbook index. --- sdk/python/packages/flet/docs/api-reference/index.md | 10 ++++++++++ sdk/python/packages/flet/docs/cookbook/index.md | 3 ++- sdk/python/packages/flet/docs/types/index.md | 12 ++++++++++++ sdk/python/packages/flet/mkdocs.yml | 4 +++- 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 sdk/python/packages/flet/docs/api-reference/index.md create mode 100644 sdk/python/packages/flet/docs/types/index.md 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..d82127d868 --- /dev/null +++ b/sdk/python/packages/flet/docs/api-reference/index.md @@ -0,0 +1,10 @@ +# API Reference + +Key entry points to the Flet API docs. + +- [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/cookbook/index.md b/sdk/python/packages/flet/docs/cookbook/index.md index dc9e927625..66e5f9e959 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. +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. 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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 4b40e80170..1f640e16a9 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: @@ -489,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 From 86bc7f0c58325f49034e135ac50fc6be6f995e28 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 10:34:42 -0800 Subject: [PATCH 20/33] Add cookbook overview macro and generalize nav rendering Introduced a new `cookbook_overview` macro for generating a dynamic overview list in the cookbook docs. Refactored and generalized the navigation rendering logic in `controls_overview.py` to support arbitrary nav paths, base directories, and skip paths, enabling reuse for different documentation sections. Updated the cookbook index to use the new macro and improved the API reference and cookbook index content. --- .../packages/flet/docs/api-reference/index.md | 2 - .../packages/flet/docs/cookbook/index.md | 4 +- .../flet/docs/extras/macros/__init__.py | 10 ++- .../docs/extras/macros/controls_overview.py | 86 +++++++++++++------ 4 files changed, 72 insertions(+), 30 deletions(-) diff --git a/sdk/python/packages/flet/docs/api-reference/index.md b/sdk/python/packages/flet/docs/api-reference/index.md index d82127d868..8d9e5a61f2 100644 --- a/sdk/python/packages/flet/docs/api-reference/index.md +++ b/sdk/python/packages/flet/docs/api-reference/index.md @@ -1,7 +1,5 @@ # API Reference -Key entry points to the Flet API docs. - - [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. diff --git a/sdk/python/packages/flet/docs/cookbook/index.md b/sdk/python/packages/flet/docs/cookbook/index.md index 66e5f9e959..a5b2711690 100644 --- a/sdk/python/packages/flet/docs/cookbook/index.md +++ b/sdk/python/packages/flet/docs/cookbook/index.md @@ -1,5 +1,5 @@ # Cookbook -The cookbook contains recipes that demonstrate how to solve common problems while writing Flet apps. +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. -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 4c9be0f4c6..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_sub_nav_overview +from .controls_overview import render_nav_overview, render_sub_nav_overview def define_env(env): @@ -134,3 +134,11 @@ def controls_overview(): @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 0115a25047..380ec4be64 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,17 @@ from typing import Any CONTROLS_INDEX_PATH = "controls/index.md" -CONTROLS_DIR = os.path.dirname(CONTROLS_INDEX_PATH) -SKIP_PATHS = {CONTROLS_INDEX_PATH} -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 +27,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 +41,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 +51,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 +62,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 +75,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 +99,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_sub_nav_overview(nav_name: str) -> 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") - api_section = _find_nav_branch(api_reference, nav_name) if api_reference else None - nodes = _build_nav_nodes(api_section) if isinstance(api_section, 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}, + ) + + if __name__ == "__main__": - print(render_sub_nav_overview("Controls")) + print( + render_nav_overview( + ["API Reference", "Controls"], + base_dir="controls", + skip_paths={CONTROLS_INDEX_PATH}, + ) + ) From 1e5c902884783301631547ecd181ca4539e7faff Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 11:20:02 -0800 Subject: [PATCH 21/33] Move examples from controls to services directory Renamed Python example files and related media from sdk/python/examples/controls to sdk/python/examples/services for consistency. Updated references in documentation and README files to point to the new services paths. Adjusted code comments and doc macros to reflect the new structure. --- .../sensors => services}/accelerometer/basic.py | 0 .../{controls => services}/audio/declarative_1.py | 0 .../{controls => services}/audio/example_1.py | 3 +-- .../audio_recorder/example_1.py | 3 +-- .../sensors => services}/barometer/basic.py | 0 .../{controls => services}/battery/basic.py | 0 .../{controls => services}/connectivity/basic.py | 0 .../file_picker/media/pick_and_upload.png | Bin .../media/pick_save_and_get_directory_path.png | Bin .../file_picker/pick_and_upload.py | 4 ++-- .../file_picker/pick_save_and_get_directory_path.py | 0 .../{controls => services}/flashlight/example_1.py | 3 +-- .../{controls => services}/geolocator/example_1.py | 3 +-- .../sensors => services}/gyroscope/basic.py | 0 .../{controls => services}/haptic_feedback/basic.py | 0 .../sensors => services}/magnetometer/basic.py | 0 .../permission_handler/example_1.py | 3 +-- .../screen_brightness/basic.py | 0 .../semantics_service/accessibility_features.py | 0 .../{controls => services}/shake_detector/basic.py | 0 .../examples/{controls => services}/share/basic.py | 0 .../user_accelerometer/basic.py | 0 .../{controls => services}/wakelock/basic.py | 0 sdk/python/packages/flet-audio-recorder/README.md | 2 +- sdk/python/packages/flet-audio/README.md | 2 +- sdk/python/packages/flet-flashlight/README.md | 2 +- sdk/python/packages/flet-geolocator/README.md | 2 +- .../packages/flet-permission-handler/README.md | 2 +- sdk/python/packages/flet/docs/audio/index.md | 2 +- .../packages/flet/docs/audio_recorder/index.md | 2 +- .../flet/docs/cookbook/file-picker-and-uploads.md | 8 ++++---- .../flet/docs/extras/macros/controls_overview.py | 5 +++-- sdk/python/packages/flet/docs/flashlight/index.md | 2 +- sdk/python/packages/flet/docs/geolocator/index.md | 2 +- .../packages/flet/docs/permission_handler/index.md | 2 +- .../packages/flet/docs/services/accelerometer.md | 2 +- sdk/python/packages/flet/docs/services/barometer.md | 2 +- sdk/python/packages/flet/docs/services/battery.md | 2 +- .../packages/flet/docs/services/connectivity.md | 2 +- .../packages/flet/docs/services/filepicker.md | 4 ++-- sdk/python/packages/flet/docs/services/gyroscope.md | 2 +- .../packages/flet/docs/services/hapticfeedback.md | 2 +- .../packages/flet/docs/services/magnetometer.md | 2 +- .../packages/flet/docs/services/screenbrightness.md | 2 +- .../packages/flet/docs/services/semanticsservice.md | 2 +- .../packages/flet/docs/services/shakedetector.md | 2 +- sdk/python/packages/flet/docs/services/share.md | 2 +- .../flet/docs/services/useraccelerometer.md | 2 +- sdk/python/packages/flet/docs/services/wakelock.md | 2 +- 49 files changed, 39 insertions(+), 43 deletions(-) rename sdk/python/examples/{controls/sensors => services}/accelerometer/basic.py (100%) rename sdk/python/examples/{controls => services}/audio/declarative_1.py (100%) rename sdk/python/examples/{controls => services}/audio/example_1.py (99%) rename sdk/python/examples/{controls => services}/audio_recorder/example_1.py (99%) rename sdk/python/examples/{controls/sensors => services}/barometer/basic.py (100%) rename sdk/python/examples/{controls => services}/battery/basic.py (100%) rename sdk/python/examples/{controls => services}/connectivity/basic.py (100%) rename sdk/python/examples/{controls => services}/file_picker/media/pick_and_upload.png (100%) rename sdk/python/examples/{controls => services}/file_picker/media/pick_save_and_get_directory_path.png (100%) rename sdk/python/examples/{controls => services}/file_picker/pick_and_upload.py (95%) rename sdk/python/examples/{controls => services}/file_picker/pick_save_and_get_directory_path.py (100%) rename sdk/python/examples/{controls => services}/flashlight/example_1.py (99%) rename sdk/python/examples/{controls => services}/geolocator/example_1.py (99%) rename sdk/python/examples/{controls/sensors => services}/gyroscope/basic.py (100%) rename sdk/python/examples/{controls => services}/haptic_feedback/basic.py (100%) rename sdk/python/examples/{controls/sensors => services}/magnetometer/basic.py (100%) rename sdk/python/examples/{controls => services}/permission_handler/example_1.py (99%) rename sdk/python/examples/{controls => services}/screen_brightness/basic.py (100%) rename sdk/python/examples/{controls => services}/semantics_service/accessibility_features.py (100%) rename sdk/python/examples/{controls => services}/shake_detector/basic.py (100%) rename sdk/python/examples/{controls => services}/share/basic.py (100%) rename sdk/python/examples/{controls/sensors => services}/user_accelerometer/basic.py (100%) rename sdk/python/examples/{controls => services}/wakelock/basic.py (100%) diff --git a/sdk/python/examples/controls/sensors/accelerometer/basic.py b/sdk/python/examples/services/accelerometer/basic.py similarity index 100% rename from sdk/python/examples/controls/sensors/accelerometer/basic.py rename to sdk/python/examples/services/accelerometer/basic.py 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 99% rename from sdk/python/examples/controls/audio_recorder/example_1.py rename to sdk/python/examples/services/audio_recorder/example_1.py index b88cbd03d8..e06907def8 100644 --- a/sdk/python/examples/controls/audio_recorder/example_1.py +++ b/sdk/python/examples/services/audio_recorder/example_1.py @@ -1,8 +1,7 @@ import logging -import flet_audio_recorder as far - import flet as ft +import flet_audio_recorder as far logging.basicConfig(level=logging.DEBUG) diff --git a/sdk/python/examples/controls/sensors/barometer/basic.py b/sdk/python/examples/services/barometer/basic.py similarity index 100% rename from sdk/python/examples/controls/sensors/barometer/basic.py rename to sdk/python/examples/services/barometer/basic.py diff --git a/sdk/python/examples/controls/battery/basic.py b/sdk/python/examples/services/battery/basic.py similarity index 100% rename from sdk/python/examples/controls/battery/basic.py rename to sdk/python/examples/services/battery/basic.py diff --git a/sdk/python/examples/controls/connectivity/basic.py b/sdk/python/examples/services/connectivity/basic.py similarity index 100% rename from sdk/python/examples/controls/connectivity/basic.py rename to sdk/python/examples/services/connectivity/basic.py 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/controls/sensors/gyroscope/basic.py b/sdk/python/examples/services/gyroscope/basic.py similarity index 100% rename from sdk/python/examples/controls/sensors/gyroscope/basic.py rename to sdk/python/examples/services/gyroscope/basic.py 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/controls/sensors/magnetometer/basic.py b/sdk/python/examples/services/magnetometer/basic.py similarity index 100% rename from sdk/python/examples/controls/sensors/magnetometer/basic.py rename to sdk/python/examples/services/magnetometer/basic.py 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/controls/screen_brightness/basic.py b/sdk/python/examples/services/screen_brightness/basic.py similarity index 100% rename from sdk/python/examples/controls/screen_brightness/basic.py rename to sdk/python/examples/services/screen_brightness/basic.py 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/controls/share/basic.py b/sdk/python/examples/services/share/basic.py similarity index 100% rename from sdk/python/examples/controls/share/basic.py rename to sdk/python/examples/services/share/basic.py diff --git a/sdk/python/examples/controls/sensors/user_accelerometer/basic.py b/sdk/python/examples/services/user_accelerometer/basic.py similarity index 100% rename from sdk/python/examples/controls/sensors/user_accelerometer/basic.py rename to sdk/python/examples/services/user_accelerometer/basic.py diff --git a/sdk/python/examples/controls/wakelock/basic.py b/sdk/python/examples/services/wakelock/basic.py similarity index 100% rename from sdk/python/examples/controls/wakelock/basic.py rename to sdk/python/examples/services/wakelock/basic.py 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-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/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/extras/macros/controls_overview.py b/sdk/python/packages/flet/docs/extras/macros/controls_overview.py index 380ec4be64..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,6 +5,7 @@ from typing import Any CONTROLS_INDEX_PATH = "controls/index.md" +SERVICES_INDEX_PATH = "services/index.md" def _relative_markdown_path(path: str, base_dir: str) -> str: @@ -145,7 +146,7 @@ def render_sub_nav_overview(nav_name: str) -> str: return render_nav_overview( ["API Reference", nav_name], base_dir=nav_name.lower(), - skip_paths={CONTROLS_INDEX_PATH}, + skip_paths={CONTROLS_INDEX_PATH, SERVICES_INDEX_PATH}, ) @@ -154,6 +155,6 @@ def render_sub_nav_overview(nav_name: str) -> str: render_nav_overview( ["API Reference", "Controls"], base_dir="controls", - skip_paths={CONTROLS_INDEX_PATH}, + 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 index badc93a570..fc9c0e01a2 100644 --- a/sdk/python/packages/flet/docs/services/accelerometer.md +++ b/sdk/python/packages/flet/docs/services/accelerometer.md @@ -1,6 +1,6 @@ --- class_name: flet.Accelerometer -examples: ../../examples/controls/sensors/accelerometer +examples: ../../examples/services/accelerometer --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/barometer.md b/sdk/python/packages/flet/docs/services/barometer.md index 154b46056d..dec579306d 100644 --- a/sdk/python/packages/flet/docs/services/barometer.md +++ b/sdk/python/packages/flet/docs/services/barometer.md @@ -1,6 +1,6 @@ --- class_name: flet.Barometer -examples: ../../examples/controls/sensors/barometer +examples: ../../examples/services/barometer --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/battery.md b/sdk/python/packages/flet/docs/services/battery.md index e62a535df9..3384cfeb77 100644 --- a/sdk/python/packages/flet/docs/services/battery.md +++ b/sdk/python/packages/flet/docs/services/battery.md @@ -1,6 +1,6 @@ --- class_name: flet.Battery -examples: ../../examples/controls/battery +examples: ../../examples/services/battery --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/connectivity.md b/sdk/python/packages/flet/docs/services/connectivity.md index 4407fad1ed..8de0f503ac 100644 --- a/sdk/python/packages/flet/docs/services/connectivity.md +++ b/sdk/python/packages/flet/docs/services/connectivity.md @@ -1,6 +1,6 @@ --- class_name: flet.Connectivity -examples: ../../examples/controls/connectivity +examples: ../../examples/services/connectivity --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/filepicker.md b/sdk/python/packages/flet/docs/services/filepicker.md index 13521c33b2..43e72fda8e 100644 --- a/sdk/python/packages/flet/docs/services/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 index 2635bb8cb0..3fe566142c 100644 --- a/sdk/python/packages/flet/docs/services/gyroscope.md +++ b/sdk/python/packages/flet/docs/services/gyroscope.md @@ -1,6 +1,6 @@ --- class_name: flet.Gyroscope -examples: ../../examples/controls/sensors/gyroscope +examples: ../../examples/services/gyroscope --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/hapticfeedback.md b/sdk/python/packages/flet/docs/services/hapticfeedback.md index 82b074a625..2a43e26a0f 100644 --- a/sdk/python/packages/flet/docs/services/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/magnetometer.md b/sdk/python/packages/flet/docs/services/magnetometer.md index f81db5356f..bb005ad90b 100644 --- a/sdk/python/packages/flet/docs/services/magnetometer.md +++ b/sdk/python/packages/flet/docs/services/magnetometer.md @@ -1,6 +1,6 @@ --- class_name: flet.Magnetometer -examples: ../../examples/controls/sensors/magnetometer +examples: ../../examples/services/magnetometer --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/screenbrightness.md b/sdk/python/packages/flet/docs/services/screenbrightness.md index 0fb8aea85d..236fb9b8ff 100644 --- a/sdk/python/packages/flet/docs/services/screenbrightness.md +++ b/sdk/python/packages/flet/docs/services/screenbrightness.md @@ -1,6 +1,6 @@ --- class_name: flet.ScreenBrightness -examples: ../../examples/controls/screen_brightness +examples: ../../examples/services/screen_brightness --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/semanticsservice.md b/sdk/python/packages/flet/docs/services/semanticsservice.md index e2b2f46ad5..5eac64f5a5 100644 --- a/sdk/python/packages/flet/docs/services/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/services/shakedetector.md b/sdk/python/packages/flet/docs/services/shakedetector.md index e5de549874..8a2759fe08 100644 --- a/sdk/python/packages/flet/docs/services/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 index d727b0077b..11f472ac3a 100644 --- a/sdk/python/packages/flet/docs/services/share.md +++ b/sdk/python/packages/flet/docs/services/share.md @@ -1,6 +1,6 @@ --- class_name: flet.Share -examples: ../../examples/controls/share +examples: ../../examples/services/share --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/useraccelerometer.md b/sdk/python/packages/flet/docs/services/useraccelerometer.md index 9e2b9d496a..ab46a5cb3a 100644 --- a/sdk/python/packages/flet/docs/services/useraccelerometer.md +++ b/sdk/python/packages/flet/docs/services/useraccelerometer.md @@ -1,6 +1,6 @@ --- class_name: flet.UserAccelerometer -examples: ../../examples/controls/sensors/user_accelerometer +examples: ../../examples/services/user_accelerometer --- {{ class_summary(class_name) }} diff --git a/sdk/python/packages/flet/docs/services/wakelock.md b/sdk/python/packages/flet/docs/services/wakelock.md index 6eeb2ca68c..a232b62c33 100644 --- a/sdk/python/packages/flet/docs/services/wakelock.md +++ b/sdk/python/packages/flet/docs/services/wakelock.md @@ -1,6 +1,6 @@ --- class_name: flet.Wakelock -examples: ../../examples/controls/wakelock +examples: ../../examples/services/wakelock --- {{ class_summary(class_name) }} From 126e1d35b9b32b0fd534a0e78e40a0a00a3f2576 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 11:54:10 -0800 Subject: [PATCH 22/33] Add basic examples and update docs for services Added basic usage examples for BrowserContextMenu, Clipboard, SharedPreferences, and StoragePaths services in the examples directory. Updated corresponding documentation files to include example code and improved structure for better clarity. --- .../services/browsercontextmenu/basic.py | 32 +++++++++++ .../examples/services/clipboard/basic.py | 25 +++++++++ .../services/sharedpreferences/basic.py | 37 +++++++++++++ .../examples/services/storagepaths/basic.py | 55 +++++++++++++++++++ .../flet/docs/services/browsercontextmenu.md | 15 ++++- .../packages/flet/docs/services/clipboard.md | 11 +++- .../flet/docs/services/sharedpreferences.md | 13 ++++- .../flet/docs/services/storagepaths.md | 13 ++++- 8 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 sdk/python/examples/services/browsercontextmenu/basic.py create mode 100644 sdk/python/examples/services/clipboard/basic.py create mode 100644 sdk/python/examples/services/sharedpreferences/basic.py create mode 100644 sdk/python/examples/services/storagepaths/basic.py 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/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/packages/flet/docs/services/browsercontextmenu.md b/sdk/python/packages/flet/docs/services/browsercontextmenu.md index 7f1fd5b629..a0d82b85b5 100644 --- a/sdk/python/packages/flet/docs/services/browsercontextmenu.md +++ b/sdk/python/packages/flet/docs/services/browsercontextmenu.md @@ -1 +1,14 @@ -{{ class_all_options("flet.BrowserContextMenu") }} +--- +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 index d25a9cfa53..c8660f7857 100644 --- a/sdk/python/packages/flet/docs/services/clipboard.md +++ b/sdk/python/packages/flet/docs/services/clipboard.md @@ -1,5 +1,14 @@ --- class_name: flet.Clipboard +examples: ../../examples/services/clipboard --- -{{ class_all_options(class_name) }} +{{ 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 index ed1f142114..a3e4350229 100644 --- a/sdk/python/packages/flet/docs/services/sharedpreferences.md +++ b/sdk/python/packages/flet/docs/services/sharedpreferences.md @@ -1,5 +1,16 @@ --- class_name: flet.SharedPreferences +examples: ../../examples/services/sharedpreferences --- -{{ class_all_options(class_name) }} +{{ 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 index ad8c43ef9c..7166597d51 100644 --- a/sdk/python/packages/flet/docs/services/storagepaths.md +++ b/sdk/python/packages/flet/docs/services/storagepaths.md @@ -1,5 +1,16 @@ --- class_name: flet.StoragePaths +examples: ../../examples/services/storagepaths --- -{{ class_all_options(class_name) }} +{{ class_summary(class_name) }} + +## Examples + +### Basic Example + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} From 783d6fc0dffcef13e09d1e2373b47322793ff2cb Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 12:45:04 -0800 Subject: [PATCH 23/33] Enhance UrlLauncher with launch modes and configuration Added support for multiple launch modes, in-app web view and browser view configurations, and popup window opening in UrlLauncher for both Dart and Python SDKs. Updated documentation and examples to reflect new features and usage. Exposed BrowserConfiguration, LaunchMode, and WebViewConfiguration in Python package exports and docs. --- .../flet/lib/src/services/url_launcher.dart | 51 +++++- packages/flet/lib/src/utils/launch_url.dart | 50 +++--- .../examples/services/urllauncher/basic.py | 81 +++++++++ .../flet/docs/services/urllauncher.md | 13 +- .../flet/docs/types/browserconfiguration.md | 1 + .../packages/flet/docs/types/launchmode.md | 1 + .../flet/docs/types/webviewconfiguration.md | 1 + sdk/python/packages/flet/mkdocs.yml | 3 + sdk/python/packages/flet/src/flet/__init__.py | 11 +- .../flet/controls/services/url_launcher.py | 162 ++++++++++++++---- 10 files changed, 316 insertions(+), 58 deletions(-) create mode 100644 sdk/python/examples/services/urllauncher/basic.py create mode 100644 sdk/python/packages/flet/docs/types/browserconfiguration.md create mode 100644 sdk/python/packages/flet/docs/types/launchmode.md create mode 100644 sdk/python/packages/flet/docs/types/webviewconfiguration.md 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/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/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/packages/flet/docs/services/urllauncher.md b/sdk/python/packages/flet/docs/services/urllauncher.md index 567293b89c..691ce990c0 100644 --- a/sdk/python/packages/flet/docs/services/urllauncher.md +++ b/sdk/python/packages/flet/docs/services/urllauncher.md @@ -1,5 +1,16 @@ --- class_name: flet.UrlLauncher +examples: ../../examples/services/urllauncher --- -{{ class_all_options(class_name) }} +{{ class_summary(class_name) }} + +## Examples + +### Basic Example + +```python +--8<-- "{{ examples }}/basic.py" +``` + +{{ class_members(class_name) }} 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/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/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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 1f640e16a9..7126e6cbf4 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -742,8 +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 + - WebViewConfiguration: types/webviewconfiguration.md - UnderlineTabIndicator: types/underlinetabindicator.md - Video: - PlaylistMode: video/types/playlist_mode.md @@ -845,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 diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 047774da8c..8dce6aaf4b 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -462,7 +462,12 @@ ) 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 from flet.controls.services.wakelock import Wakelock from flet.controls.template_route import TemplateRoute @@ -637,6 +642,8 @@ "BoxShadowValue", "BoxShape", "Brightness", + "BrowserConfiguration", + "BrowserConfiguration", "BrowserContextMenu", "Button", "ButtonStyle", @@ -807,6 +814,7 @@ "KeyboardListener", "KeyboardType", "LabelPosition", + "LaunchMode", "LayoutControl", "LinearGradient", "LinuxDeviceInfo", @@ -1034,6 +1042,7 @@ "WebBrowserName", "WebDeviceInfo", "WebRenderer", + "WebViewConfiguration", "Window", "WindowDragArea", "WindowEvent", 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 9be472b733..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,10 +1,64 @@ +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") @@ -17,29 +71,29 @@ 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, }, ) @@ -53,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} + ) From 6d94a39111152875cdc6571756933a0940ef3c13 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 13:21:45 -0800 Subject: [PATCH 24/33] Refactor service registry usage in Page control Consolidated service registries in the Page control by removing separate _user_services and _page_services in favor of a single _services property. Updated related Dart and Python code, including tests and service registration logic, to reflect this change. Deprecated direct service properties on Page in favor of using service classes directly. --- packages/flet/lib/src/controls/page.dart | 35 ++-- .../transport/flet_backend_channel_mock.dart | 3 +- .../packages/flet/src/flet/controls/page.py | 150 +++++++++++------- .../src/flet/controls/services/service.py | 2 +- .../flet/src/flet/messaging/session.py | 2 +- .../flet/tests/test_object_diff_in_place.py | 26 +-- 6 files changed, 113 insertions(+), 105 deletions(-) diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index 1cc8f65d78..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; @@ -136,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(); } @@ -152,24 +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/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/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 2958ac5a70..45d7267778 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=lambda: ServiceRegistry()) def __post_init__( self, @@ -437,13 +397,6 @@ def __post_init__( 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 +536,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 +689,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 +754,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 +782,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 +817,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 +890,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/service.py b/sdk/python/packages/flet/src/flet/controls/services/service.py index 3a30c13d8b..bcc080862a 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/service.py +++ b/sdk/python/packages/flet/src/flet/controls/services/service.py @@ -16,4 +16,4 @@ class Service(BaseControl): 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/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..ec1cc34153 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) @@ -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, }, ], From 64fb1a1ed3e3ca5a275427d3f1d648f6ea8106a5 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 19:24:38 -0800 Subject: [PATCH 25/33] Update commented key from _user_services to _services Changed a commented dictionary key in test_object_diff_in_place.py from '_user_services' to '_services' for consistency with the underlying code or data structure. --- sdk/python/packages/flet/tests/test_object_diff_in_place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ec1cc34153..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 @@ -169,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": [ From 2879a68c1fdf8189cf68af5cb2270fb8aba58857 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 19:45:59 -0800 Subject: [PATCH 26/33] Update Flutter dependencies to latest versions Upgraded battery_plus to 7.0.0, connectivity_plus to 7.0.0, device_info_plus to 12.3.0, and screen_brightness to 2.1.7 in packages/flet/pubspec.yaml. This ensures compatibility with the latest features and bug fixes from these packages. --- client/pubspec.lock | 8 ++++---- packages/flet/pubspec.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 4356782da1..e768a501b1 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: battery_plus - sha256: "03d5a6bb36db9d2b977c548f6b0262d5a84c4d5a4cfee2edac4a91d57011b365" + sha256: ad16fcb55b7384be6b4bbc763d5e2031ac7ea62b2d9b6b661490c7b9741155bf url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "7.0.0" battery_plus_platform_interface: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "7.0.0" connectivity_plus_platform_interface: dependency: transitive description: diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 2829a50d2d..e0f280973f 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -21,10 +21,10 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - battery_plus: ^6.2.2 + battery_plus: ^7.0.0 collection: ^1.19.0 - connectivity_plus: ^6.1.2 - 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 @@ -38,7 +38,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.2 screenshot: ^3.0.0 - screen_brightness: ^2.1.0 + screen_brightness: ^2.1.7 sensors_plus: ^7.0.0 shared_preferences: 2.5.3 share_plus: ^12.0.1 From b019017c414c62b7a90a250c007fdacafb49b568 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Dec 2025 19:54:22 -0800 Subject: [PATCH 27/33] Add file sharing from path example Introduces a new function to share files from a file path, including UI updates to provide separate buttons for sharing files from bytes and from paths. File sharing from paths is disabled on the web platform. --- sdk/python/examples/services/share/basic.py | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/sdk/python/examples/services/share/basic.py b/sdk/python/examples/services/share/basic.py index 02e55b64d6..f600389ccd 100644 --- a/sdk/python/examples/services/share/basic.py +++ b/sdk/python/examples/services/share/basic.py @@ -1,3 +1,5 @@ +import os + import flet as ft @@ -21,7 +23,7 @@ async def do_share_uri(): status.value = f"Share status: {result.status}" result_raw.value = f"Raw: {result.raw}" - async def do_share_files(): + async def do_share_files_from_bytes(): file = ft.ShareFile.from_bytes( b"Sample content from memory", mime_type="text/plain", @@ -34,6 +36,23 @@ async def do_share_files(): 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( @@ -42,7 +61,14 @@ async def do_share_files(): [ ft.Button("Share text", on_click=do_share_text), ft.Button("Share link", on_click=do_share_uri), - ft.Button("Share file", on_click=do_share_files), + 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, ), From 6af6b5918e3c744338df3f48317fd216f3cfacdc Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Dec 2025 10:42:28 -0800 Subject: [PATCH 28/33] Set pixel_ratio in outlined button screenshot tests Updated screenshot assertions in the handling_clicks test to specify the pixel_ratio parameter, ensuring screenshots are taken with the correct scaling. --- .../examples/material/test_outlined_button.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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( From b0359ff7d6797f2c62b94c598ba1eef4d5530f6a Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Dec 2025 11:15:15 -0800 Subject: [PATCH 29/33] Remove duplicate and redundant code in Flet package Eliminated a duplicate line in the ShareFile documentation, removed a repeated entry for 'BrowserConfiguration' in __all__, and simplified the ServiceRegistry default_factory in Page control. These changes improve code clarity and maintainability. --- sdk/python/packages/flet/docs/types/sharefile.md | 1 - sdk/python/packages/flet/src/flet/__init__.py | 1 - sdk/python/packages/flet/src/flet/controls/page.py | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sdk/python/packages/flet/docs/types/sharefile.md b/sdk/python/packages/flet/docs/types/sharefile.md index 14ac050138..d396822daf 100644 --- a/sdk/python/packages/flet/docs/types/sharefile.md +++ b/sdk/python/packages/flet/docs/types/sharefile.md @@ -1,2 +1 @@ {{ class_all_options("flet.ShareFile", separate_signature=False) }} -{{ class_all_options("flet.ShareFile", separate_signature=False) }} diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 8dce6aaf4b..3de77e438c 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -643,7 +643,6 @@ "BoxShape", "Brightness", "BrowserConfiguration", - "BrowserConfiguration", "BrowserContextMenu", "Button", "ButtonStyle", diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 45d7267778..573b6b66fa 100644 --- a/sdk/python/packages/flet/src/flet/controls/page.py +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -385,7 +385,7 @@ class Page(BasePage): """ TBD """ - _services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry()) + _services: ServiceRegistry = field(default_factory=ServiceRegistry) def __post_init__( self, @@ -395,8 +395,6 @@ def __post_init__( BasePage.__post_init__(self, ref) self._i = 1 self.__session = weakref.ref(sess) - - # page services self.__last_route = None self.__query: QueryString = QueryString(self) self.__authorization: Optional[Authorization] = None From e79055e5cc7a4ca8b54afc61b6b809f4e65ea18c Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Dec 2025 11:56:31 -0800 Subject: [PATCH 30/33] Refactor sensor services and event models Split sensor services into individual Dart files and refactor base class for sensor streams. In Python, move sensor event models into their respective service modules, update event timestamp types to datetime, and remove the shared sensor_events.py file. Update imports and public API accordingly for improved modularity and clarity. --- .../flet/lib/src/flet_core_extension.dart | 6 +- .../flet/lib/src/services/accelerometer.dart | 22 +++ packages/flet/lib/src/services/barometer.dart | 20 +++ .../{sensors.dart => base_sensor.dart} | 98 +---------- packages/flet/lib/src/services/gyroscope.dart | 22 +++ .../flet/lib/src/services/magnetometer.dart | 22 +++ .../lib/src/services/user_accelerometer.dart | 23 +++ sdk/python/packages/flet/src/flet/__init__.py | 25 ++- .../flet/controls/services/accelerometer.py | 33 +++- .../src/flet/controls/services/barometer.py | 36 ++++- .../src/flet/controls/services/gyroscope.py | 33 +++- .../flet/controls/services/magnetometer.py | 39 ++++- .../controls/services/sensor_error_event.py | 15 ++ .../flet/controls/services/sensor_events.py | 153 ------------------ .../controls/services/user_accelerometer.py | 33 +++- 15 files changed, 287 insertions(+), 293 deletions(-) create mode 100644 packages/flet/lib/src/services/accelerometer.dart create mode 100644 packages/flet/lib/src/services/barometer.dart rename packages/flet/lib/src/services/{sensors.dart => base_sensor.dart} (52%) create mode 100644 packages/flet/lib/src/services/gyroscope.dart create mode 100644 packages/flet/lib/src/services/magnetometer.dart create mode 100644 packages/flet/lib/src/services/user_accelerometer.dart create mode 100644 sdk/python/packages/flet/src/flet/controls/services/sensor_error_event.py delete mode 100644 sdk/python/packages/flet/src/flet/controls/services/sensor_events.py diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart index b34ba7bba6..eb633ca196 100644 --- a/packages/flet/lib/src/flet_core_extension.dart +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -108,14 +108,17 @@ 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/sensors.dart'; import 'services/shared_preferences.dart'; import 'services/screen_brightness.dart'; import 'services/storage_paths.dart'; @@ -123,6 +126,7 @@ 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'; 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/sensors.dart b/packages/flet/lib/src/services/base_sensor.dart similarity index 52% rename from packages/flet/lib/src/services/sensors.dart rename to packages/flet/lib/src/services/base_sensor.dart index 2694cca56f..f7b4831c4e 100644 --- a/packages/flet/lib/src/services/sensors.dart +++ b/packages/flet/lib/src/services/base_sensor.dart @@ -7,8 +7,8 @@ import '../flet_service.dart'; import '../utils/numbers.dart'; import '../utils/time.dart'; -abstract class _SensorStreamService extends FletService { - _SensorStreamService({required super.control}); +abstract class BaseSensorService extends FletService { + BaseSensorService({required super.control}); StreamSubscription? _subscription; bool _enabled = true; @@ -102,97 +102,3 @@ abstract class _SensorStreamService extends FletService { super.dispose(); } } - -class AccelerometerService extends _SensorStreamService { - 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.microsecondsSinceEpoch, - }; - } -} - -class UserAccelerometerService - extends _SensorStreamService { - 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, - }; - } -} - -class GyroscopeService extends _SensorStreamService { - 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.microsecondsSinceEpoch, - }; - } -} - -class MagnetometerService extends _SensorStreamService { - 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.microsecondsSinceEpoch, - }; - } -} - -class BarometerService extends _SensorStreamService { - 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.microsecondsSinceEpoch, - }; - } -} 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/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/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 3de77e438c..1adafa834a 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -414,8 +414,11 @@ ScrollDirection, ScrollType, ) -from flet.controls.services.accelerometer import Accelerometer -from flet.controls.services.barometer import Barometer +from flet.controls.services.accelerometer import ( + Accelerometer, + AccelerometerReadingEvent, +) +from flet.controls.services.barometer import Barometer, BarometerReadingEvent from flet.controls.services.battery import ( Battery, BatteryState, @@ -435,22 +438,15 @@ FilePickerUploadEvent, FilePickerUploadFile, ) -from flet.controls.services.gyroscope import Gyroscope +from flet.controls.services.gyroscope import Gyroscope, GyroscopeReadingEvent from flet.controls.services.haptic_feedback import HapticFeedback -from flet.controls.services.magnetometer import Magnetometer +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_events import ( - AccelerometerReadingEvent, - BarometerReadingEvent, - GyroscopeReadingEvent, - MagnetometerReadingEvent, - SensorErrorEvent, - UserAccelerometerReadingEvent, -) +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 ( @@ -468,7 +464,10 @@ UrlLauncher, WebViewConfiguration, ) -from flet.controls.services.user_accelerometer import UserAccelerometer +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 ( diff --git a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py index d0c8060768..19aa49442e 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -1,15 +1,36 @@ +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 EventHandler +from flet.controls.control_event import Event, EventHandler from flet.controls.duration import Duration -from flet.controls.services.sensor_events import ( - AccelerometerReadingEvent, - SensorErrorEvent, -) +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service -__all__ = ["Accelerometer"] +__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") diff --git a/sdk/python/packages/flet/src/flet/controls/services/barometer.py b/sdk/python/packages/flet/src/flet/controls/services/barometer.py index c5d4ff335f..b24d814b3b 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/barometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/barometer.py @@ -1,15 +1,39 @@ +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 EventHandler +from flet.controls.control_event import Event, EventHandler from flet.controls.duration import Duration -from flet.controls.services.sensor_events import ( - BarometerReadingEvent, - SensorErrorEvent, -) +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service -__all__ = ["Barometer"] +__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") diff --git a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py index 833fd9cb35..d02a809cc3 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -1,15 +1,36 @@ +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 EventHandler +from flet.controls.control_event import Event, EventHandler from flet.controls.duration import Duration -from flet.controls.services.sensor_events import ( - GyroscopeReadingEvent, - SensorErrorEvent, -) +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service -__all__ = ["Gyroscope"] +__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") diff --git a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py index 664a5cf0f6..82596c0e93 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py @@ -1,15 +1,42 @@ +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 EventHandler +from flet.controls.control_event import Event, EventHandler from flet.controls.duration import Duration -from flet.controls.services.sensor_events import ( - MagnetometerReadingEvent, - SensorErrorEvent, -) +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service -__all__ = ["Magnetometer"] +__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") 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/sensor_events.py b/sdk/python/packages/flet/src/flet/controls/services/sensor_events.py deleted file mode 100644 index 3464084b7b..0000000000 --- a/sdk/python/packages/flet/src/flet/controls/services/sensor_events.py +++ /dev/null @@ -1,153 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import TYPE_CHECKING - -from flet.controls.control_event import Event, EventControlType - -if TYPE_CHECKING: - from flet.controls.services.accelerometer import Accelerometer # noqa: F401 - from flet.controls.services.barometer import Barometer # noqa: F401 - from flet.controls.services.gyroscope import Gyroscope # noqa: F401 - from flet.controls.services.magnetometer import Magnetometer # noqa: F401 - from flet.controls.services.user_accelerometer import ( - UserAccelerometer, # noqa: F401 - ) - -__all__ = [ - "AccelerometerReadingEvent", - "BarometerReadingEvent", - "GyroscopeReadingEvent", - "MagnetometerReadingEvent", - "SensorErrorEvent", - "UserAccelerometerReadingEvent", -] - - -@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: int - """Event timestamp, expressed in microseconds since epoch.""" - - -@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: int - """Event timestamp, expressed in microseconds since epoch.""" - - -@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: int - """Event timestamp, expressed in microseconds since epoch.""" - - -@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: int - """Event timestamp, expressed in microseconds since epoch.""" - - -@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: int - """Event timestamp, expressed in microseconds since epoch.""" - - -@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/user_accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py index 8f210dd0a1..99e4f1e385 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -1,15 +1,36 @@ +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 EventHandler +from flet.controls.control_event import Event, EventHandler from flet.controls.duration import Duration -from flet.controls.services.sensor_events import ( - SensorErrorEvent, - UserAccelerometerReadingEvent, -) +from flet.controls.services.sensor_error_event import SensorErrorEvent from flet.controls.services.service import Service -__all__ = ["UserAccelerometer"] +__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") From 4ddf171219a918d433faab30c80bb99eb8516a7f Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Dec 2025 12:02:23 -0800 Subject: [PATCH 31/33] Refactor sensor example apps to use page.add and append Updated sensor service example scripts to use page.add for UI controls and page.services.append for service registration, replacing previous usage of page.controls and assignment to page.services. Also removed unnecessary variable declarations and logging setup for improved clarity and consistency. --- .../examples/services/accelerometer/basic.py | 12 +++++------ .../services/audio_recorder/example_1.py | 4 ---- .../examples/services/barometer/basic.py | 14 ++++++------- .../examples/services/gyroscope/basic.py | 14 ++++++------- .../examples/services/magnetometer/basic.py | 14 ++++++------- .../services/user_accelerometer/basic.py | 20 +++++++++---------- 6 files changed, 37 insertions(+), 41 deletions(-) diff --git a/sdk/python/examples/services/accelerometer/basic.py b/sdk/python/examples/services/accelerometer/basic.py index c46a3e69a7..e88f09475d 100644 --- a/sdk/python/examples/services/accelerometer/basic.py +++ b/sdk/python/examples/services/accelerometer/basic.py @@ -2,9 +2,6 @@ def main(page: ft.Page): - intro = ft.Text("Move your device to see accelerometer readings.") - reading = ft.Text("Waiting for data...") - 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() @@ -12,16 +9,19 @@ def handle_reading(e: ft.AccelerometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Accelerometer error: {e.message}")) - page.services = [ + page.services.append( ft.Accelerometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=100), cancel_on_error=False, ) - ] + ) - page.controls = [intro, reading] + 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/services/audio_recorder/example_1.py b/sdk/python/examples/services/audio_recorder/example_1.py index e06907def8..9a0502ec15 100644 --- a/sdk/python/examples/services/audio_recorder/example_1.py +++ b/sdk/python/examples/services/audio_recorder/example_1.py @@ -1,10 +1,6 @@ -import logging - import flet as ft import flet_audio_recorder as far -logging.basicConfig(level=logging.DEBUG) - def main(page: ft.Page): page.horizontal_alignment = ft.CrossAxisAlignment.CENTER diff --git a/sdk/python/examples/services/barometer/basic.py b/sdk/python/examples/services/barometer/basic.py index 0e07fcb30b..5242711a46 100644 --- a/sdk/python/examples/services/barometer/basic.py +++ b/sdk/python/examples/services/barometer/basic.py @@ -2,9 +2,6 @@ def main(page: ft.Page): - intro = ft.Text("Atmospheric pressure (hPa).") - reading = ft.Text("Waiting for data...") - def handle_reading(e: ft.BarometerReadingEvent): reading.value = f"{e.pressure:.2f} hPa" page.update() @@ -12,15 +9,18 @@ def handle_reading(e: ft.BarometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Barometer error: {e.message}")) - page.services = [ + page.services.append( ft.Barometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=500), - ), - ] + ) + ) - page.controls = [intro, reading] + page.add( + ft.Text("Atmospheric pressure (hPa)."), + reading := ft.Text("Waiting for data..."), + ) ft.run(main) diff --git a/sdk/python/examples/services/gyroscope/basic.py b/sdk/python/examples/services/gyroscope/basic.py index fe79154da9..a54c9d9b7e 100644 --- a/sdk/python/examples/services/gyroscope/basic.py +++ b/sdk/python/examples/services/gyroscope/basic.py @@ -2,9 +2,6 @@ def main(page: ft.Page): - intro = ft.Text("Rotate your device to see gyroscope readings.") - reading = ft.Text("Waiting for data...") - 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() @@ -12,15 +9,18 @@ def handle_reading(e: ft.GyroscopeReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Gyroscope error: {e.message}")) - page.services = [ + page.services.append( ft.Gyroscope( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=100), - ), - ] + ) + ) - page.controls = [intro, reading] + 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/services/magnetometer/basic.py b/sdk/python/examples/services/magnetometer/basic.py index f1d389d561..5790dc85ae 100644 --- a/sdk/python/examples/services/magnetometer/basic.py +++ b/sdk/python/examples/services/magnetometer/basic.py @@ -2,9 +2,6 @@ def main(page: ft.Page): - intro = ft.Text("Monitor the ambient magnetic field (uT).") - reading = ft.Text("Waiting for data...") - 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() @@ -12,15 +9,18 @@ def handle_reading(e: ft.MagnetometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"Magnetometer error: {e.message}")) - page.services = [ + page.services.append( ft.Magnetometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=200), - ), - ] + ) + ) - page.controls = [intro, reading] + 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/services/user_accelerometer/basic.py b/sdk/python/examples/services/user_accelerometer/basic.py index 655d90efec..db6067e7e3 100644 --- a/sdk/python/examples/services/user_accelerometer/basic.py +++ b/sdk/python/examples/services/user_accelerometer/basic.py @@ -2,12 +2,6 @@ def main(page: ft.Page): - intro = ft.Text( - "Linear acceleration without gravity. " - "Keep the app running on a device with motion sensors." - ) - reading = ft.Text("Waiting for data...") - 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() @@ -15,15 +9,21 @@ def handle_reading(e: ft.UserAccelerometerReadingEvent): def handle_error(e: ft.SensorErrorEvent): page.add(ft.Text(f"UserAccelerometer error: {e.message}")) - page.services = [ + page.services.append( ft.UserAccelerometer( on_reading=handle_reading, on_error=handle_error, interval=ft.Duration(milliseconds=100), - ), - ] + ) + ) - page.controls = [intro, reading] + 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) From a01bcc7a392e28e6e3eb7fa15afdb7b07b037292 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Dec 2025 12:53:14 -0800 Subject: [PATCH 32/33] Refactor connectivity API and add platform checks Renamed connectivity methods and event handlers for consistency (e.g., 'check_connectivity' to 'get_connectivity', 'on_connectivity_change' to 'on_change'). Updated enum name from ConnectivityResult to ConnectivityType. Added platform support checks to sensor and service classes, raising FletUnsupportedPlatformException on unsupported platforms. Improved documentation to clarify supported platforms for each service. --- .../flet/lib/src/services/connectivity.dart | 8 ++++---- .../examples/services/connectivity/basic.py | 4 ++-- sdk/python/packages/flet/src/flet/__init__.py | 4 ++-- .../flet/controls/services/accelerometer.py | 9 ++++++++- .../src/flet/controls/services/barometer.py | 7 +++++++ .../src/flet/controls/services/connectivity.py | 18 +++++++++--------- .../src/flet/controls/services/gyroscope.py | 9 ++++++++- .../src/flet/controls/services/magnetometer.py | 9 ++++++++- .../controls/services/screen_brightness.py | 7 +++++++ .../controls/services/user_accelerometer.py | 9 ++++++++- 10 files changed, 63 insertions(+), 21 deletions(-) diff --git a/packages/flet/lib/src/services/connectivity.dart b/packages/flet/lib/src/services/connectivity.dart index 7085b5f466..1fabe03b96 100644 --- a/packages/flet/lib/src/services/connectivity.dart +++ b/packages/flet/lib/src/services/connectivity.dart @@ -27,7 +27,7 @@ class ConnectivityService extends FletService { Future _invokeMethod(String name, dynamic args) async { switch (name) { - case "check_connectivity": + case "get_connectivity": final results = await _connectivity.checkConnectivity(); return results.map((r) => r.name).toList(); default: @@ -36,12 +36,12 @@ class ConnectivityService extends FletService { } void _updateListeners() { - final listenChange = control.getBool("on_connectivity_change") == true; + final listenChange = control.getBool("on_change") == true; if (listenChange && _subscription == null) { _subscription = _connectivity.onConnectivityChanged.listen( (List result) { - control.triggerEvent("connectivity_change", - {"connectivity": result.map((r) => r.name).toList()}); + control.triggerEvent( + "change", {"connectivity": result.map((r) => r.name).toList()}); }, onError: (error) { debugPrint( "ConnectivityService: error listening to connectivity: $error"); diff --git a/sdk/python/examples/services/connectivity/basic.py b/sdk/python/examples/services/connectivity/basic.py index 24f4bb062a..895c15f1ad 100644 --- a/sdk/python/examples/services/connectivity/basic.py +++ b/sdk/python/examples/services/connectivity/basic.py @@ -8,7 +8,7 @@ async def main(page: ft.Page): changes = ft.Text() async def refresh(_=None): - results = await connectivity.check_connectivity() + results = await connectivity.get_connectivity() status.value = "Current connectivity: " + ", ".join(r.value for r in results) async def on_change(e: ft.ConnectivityChangeEvent): @@ -17,7 +17,7 @@ async def on_change(e: ft.ConnectivityChangeEvent): ) await refresh() - connectivity.on_connectivity_change = on_change + connectivity.on_change = on_change await refresh() diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 1adafa834a..ae213adddc 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -429,7 +429,7 @@ from flet.controls.services.connectivity import ( Connectivity, ConnectivityChangeEvent, - ConnectivityResult, + ConnectivityType, ) from flet.controls.services.file_picker import ( FilePicker, @@ -666,7 +666,7 @@ "Component", "Connectivity", "ConnectivityChangeEvent", - "ConnectivityResult", + "ConnectivityType", "ConstrainedControl", "Container", "Context", diff --git a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py index 19aa49442e..68825c6750 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/accelerometer.py @@ -7,6 +7,7 @@ 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"] @@ -54,7 +55,7 @@ class Accelerometer(Service): Accelerometer reports zero acceleration if the device is free falling. Note: - * Supported platforms: Android, iOS. + * Supported platforms: Android, iOS and web. * Web ignores requested sampling intervals. """ @@ -90,3 +91,9 @@ class Accelerometer(Service): 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 index b24d814b3b..8773b21f19 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/barometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/barometer.py @@ -5,6 +5,7 @@ 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 @@ -96,3 +97,9 @@ class Barometer(Service): 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/connectivity.py b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py index 8ece8d96b5..8da43804e1 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/connectivity.py +++ b/sdk/python/packages/flet/src/flet/controls/services/connectivity.py @@ -6,10 +6,10 @@ from flet.controls.control_event import Event, EventHandler from flet.controls.services.service import Service -__all__ = ["Connectivity", "ConnectivityChangeEvent", "ConnectivityResult"] +__all__ = ["Connectivity", "ConnectivityChangeEvent", "ConnectivityType"] -class ConnectivityResult(Enum): +class ConnectivityType(Enum): """ Connectivity states. """ @@ -48,9 +48,9 @@ class ConnectivityResult(Enum): class ConnectivityChangeEvent(Event["Connectivity"]): """Event fired when connectivity changes.""" - connectivity: list[ConnectivityResult] + connectivity: list[ConnectivityType] """ - Current connectivity state(s). + Current connectivity type(s). """ @@ -60,15 +60,15 @@ class Connectivity(Service): Provides device connectivity status and change notifications. """ - on_connectivity_change: Optional[EventHandler[ConnectivityChangeEvent]] = None + on_change: Optional[EventHandler[ConnectivityChangeEvent]] = None """ Called when connectivity changes. """ - async def check_connectivity(self) -> list[ConnectivityResult]: + async def get_connectivity(self) -> list[ConnectivityType]: """ - Returns the current connectivity state(s). + Returns the current connectivity type(s). """ - result = await self._invoke_method("check_connectivity") - return [ConnectivityResult(r) for r in result] + 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 index d02a809cc3..5f55217c59 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py +++ b/sdk/python/packages/flet/src/flet/controls/services/gyroscope.py @@ -5,6 +5,7 @@ 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 @@ -40,7 +41,7 @@ class Gyroscope(Service): reporting device rotation rate around each axis in `rad/s`. Note: - * Supported platforms: Android, iOS. + * Supported platforms: Android, iOS and web. * Web ignores requested sampling intervals. """ @@ -73,3 +74,9 @@ class Gyroscope(Service): 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 index 82596c0e93..71ced20b8f 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/magnetometer.py @@ -5,6 +5,7 @@ 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 @@ -48,7 +49,7 @@ class Magnetometer(Service): Note: * Supported platforms: Android, iOS. - * Magnetometer APIs are not available on web + * Magnetometer APIs are not available on web. or desktop, so always handle `on_error` to detect unsupported hardware. """ @@ -81,3 +82,9 @@ class Magnetometer(Service): 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 index 873c94a071..c73d442934 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py +++ b/sdk/python/packages/flet/src/flet/controls/services/screen_brightness.py @@ -3,6 +3,7 @@ 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 @@ -125,3 +126,9 @@ async def set_auto_reset(self, auto_reset: bool): """ 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/user_accelerometer.py b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py index 99e4f1e385..7405a28999 100644 --- a/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py +++ b/sdk/python/packages/flet/src/flet/controls/services/user_accelerometer.py @@ -5,6 +5,7 @@ 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 @@ -47,7 +48,7 @@ class UserAccelerometer(Service): from [`AccelerometerReadingEvent`][flet.]. Note: - * Supported platforms: Android, iOS. + * Supported platforms: Android, iOS and web. * Web ignores requested sampling intervals. """ @@ -80,3 +81,9 @@ class UserAccelerometer(Service): 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." + ) From 49afbe6551e55d654f7daa9f3c64c40562065ed9 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 9 Dec 2025 10:57:23 -0800 Subject: [PATCH 33/33] Replace ConnectivityResult with ConnectivityType in docs Removed the documentation for ConnectivityResult and added documentation for ConnectivityType. Updated mkdocs.yml navigation to reference ConnectivityType instead of ConnectivityResult. --- sdk/python/packages/flet/docs/types/connectivityresult.md | 1 - sdk/python/packages/flet/docs/types/connectivitytype.md | 1 + sdk/python/packages/flet/mkdocs.yml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 sdk/python/packages/flet/docs/types/connectivityresult.md create mode 100644 sdk/python/packages/flet/docs/types/connectivitytype.md diff --git a/sdk/python/packages/flet/docs/types/connectivityresult.md b/sdk/python/packages/flet/docs/types/connectivityresult.md deleted file mode 100644 index 2b48530584..0000000000 --- a/sdk/python/packages/flet/docs/types/connectivityresult.md +++ /dev/null @@ -1 +0,0 @@ -{{ class_all_options("flet.ConnectivityResult", 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/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 7126e6cbf4..4387ed6b6e 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -864,7 +864,7 @@ nav: - BarometerReadingEvent: types/barometerreadingevent.md - BatteryState: types/batterystate.md - BatteryStateChangeEvent: types/batterystatechangeevent.md - - ConnectivityResult: types/connectivityresult.md + - ConnectivityType: types/connectivitytype.md - ConnectivityChangeEvent: types/connectivitychangeevent.md - CanvasResizeEvent: types/canvasresizeevent.md - ContextMenuDismissEvent: types/contextmenudismissevent.md