diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist index 31022f0d2..0f48cb773 100644 --- a/client/ios/Runner/Info.plist +++ b/client/ios/Runner/Info.plist @@ -58,7 +58,9 @@ NSMicrophoneUsageDescription Audio Recording + NSCameraUsageDescription + App requires access to camera NSLocationWhenInUseUsageDescription - This app needs access to location when open. + App requires access to Location \ No newline at end of file diff --git a/client/lib/main.dart b/client/lib/main.dart index 6198b19ef..d95989bc2 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flet/flet.dart'; import 'package:flet_audio/flet_audio.dart' as flet_audio; import 'package:flet_audio_recorder/flet_audio_recorder.dart' as flet_audio_recorder; +import 'package:flet_camera/flet_camera.dart' as flet_camera; import 'package:flet_geolocator/flet_geolocator.dart' as flet_geolocator; import 'package:flet_permission_handler/flet_permission_handler.dart' as flet_permission_handler; import 'package:flet_lottie/flet_lottie.dart' as flet_lottie; @@ -35,6 +36,7 @@ void main([List? args]) async { flet_rive.ensureInitialized(); flet_video.ensureInitialized(); flet_webview.ensureInitialized(); + flet_camera.ensureInitialized(); var pageUrl = Uri.base.toString(); var assetsDir = ""; @@ -100,7 +102,8 @@ void main([List? args]) async { flet_map.createControl, flet_rive.createControl, flet_video.createControl, - flet_webview.createControl + flet_webview.createControl, + flet_camera.createControl ], )); } diff --git a/client/pubspec.lock b/client/pubspec.lock index a483165ed..7e18b2a9a 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -89,6 +89,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + camera: + dependency: transitive + description: + name: camera + sha256: cf8ed1789aa244392cfc49d13e97879700476ba641c92500e01f630e109c0f6c + url: "https://pub.dev" + source: hosted + version: "0.11.0" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "59967e6d80df9d682a33b86f228cc524e6b52d6184b84f6ac62151dd98bd1ea0" + url: "https://pub.dev" + source: hosted + version: "0.6.5+2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "7d021e8cd30d9b71b8b92b4ad669e80af432d722d18d6aac338572754a786c15" + url: "https://pub.dev" + source: hosted + version: "0.9.16" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: a250314a48ea337b35909a4c9d5416a208d736dcb01d0b02c6af122be66660b0 + url: "https://pub.dev" + source: hosted + version: "2.7.4" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "9e9aba2fbab77ce2472924196ff8ac4dd8f9126c4f9a3096171cd1d870d6b26c" + url: "https://pub.dev" + source: hosted + version: "0.3.3" characters: dependency: transitive description: @@ -246,6 +286,13 @@ packages: relative: true source: path version: "0.22.1" + flet_camera: + dependency: "direct main" + description: + path: "../packages/flet_camera" + relative: true + source: path + version: "0.22.1" flet_geolocator: dependency: "direct main" description: @@ -1120,6 +1167,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 21af746bf..45e748cfa 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -51,6 +51,8 @@ dependencies: path: ../packages/flet_video flet_webview: path: ../packages/flet_webview + flet_camera: + path: ../packages/flet_camera url_strategy: ^0.2.0 cupertino_icons: ^1.0.6 diff --git a/packages/flet_camera/.gitignore b/packages/flet_camera/.gitignore new file mode 100644 index 000000000..e050eb512 --- /dev/null +++ b/packages/flet_camera/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ +.flutter-plugins +.flutter-plugins-dependencies \ No newline at end of file diff --git a/packages/flet_camera/.metadata b/packages/flet_camera/.metadata new file mode 100644 index 000000000..07d8623a3 --- /dev/null +++ b/packages/flet_camera/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" + channel: "stable" + +project_type: package diff --git a/packages/flet_camera/CHANGELOG.md b/packages/flet_camera/CHANGELOG.md new file mode 100644 index 000000000..263b8cb65 --- /dev/null +++ b/packages/flet_camera/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.23.0 + +Initial release of the package. \ No newline at end of file diff --git a/packages/flet_camera/LICENSE b/packages/flet_camera/LICENSE new file mode 100644 index 000000000..f49a4e16e --- /dev/null +++ b/packages/flet_camera/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/flet_camera/README.md b/packages/flet_camera/README.md new file mode 100644 index 000000000..dfe757796 --- /dev/null +++ b/packages/flet_camera/README.md @@ -0,0 +1,3 @@ +# Flet `Camera` control + +`Camera` control to use in Flet apps. \ No newline at end of file diff --git a/packages/flet_camera/analysis_options.yaml b/packages/flet_camera/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/packages/flet_camera/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flet_camera/lib/flet_camera.dart b/packages/flet_camera/lib/flet_camera.dart new file mode 100644 index 000000000..55a66b18c --- /dev/null +++ b/packages/flet_camera/lib/flet_camera.dart @@ -0,0 +1,3 @@ +library flet_camera; + +export "src/create_control.dart" show createControl, ensureInitialized; diff --git a/packages/flet_camera/lib/src/camera.dart b/packages/flet_camera/lib/src/camera.dart new file mode 100644 index 000000000..0ddf29ec9 --- /dev/null +++ b/packages/flet_camera/lib/src/camera.dart @@ -0,0 +1,178 @@ +import 'package:camera/camera.dart'; +import 'package:flet/flet.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../utils/camera.dart'; + +class CameraControl extends StatefulWidget { + final Control? parent; + final Control control; + final List children; + final bool parentDisabled; + final FletControlBackend backend; + + const CameraControl( + {super.key, + required this.parent, + required this.control, + required this.children, + required this.parentDisabled, + required this.backend}); + + @override + State createState() => _CameraControlState(); +} + +class _CameraControlState extends State { + CameraController? _controller; + + Future> _initCameras() async { + var cameras = await availableCameras(); + var resolutionPreset = + parseResolutionPreset(widget.control.attrString("resolutionPreset")); + var imageFormatGroup = + parseImageFormatGroup(widget.control.attrString("imageFormatGroup")); + var enableAudio = widget.control.attrBool("enableAudio", true)!; + // setState(() { + _controller = CameraController( + cameras[0], ResolutionPreset.medium, + enableAudio: enableAudio, //imageFormatGroup: imageFormatGroup + ); + // }); + return cameras; + } + + @override + void initState() { + super.initState(); + var cameras = _initCameras(); + + debugPrint("Camera.initState($cameras)"); + _controller?.initialize().then((_) { + if (!mounted) { + return; + } + setState(() {}); + }).catchError((Object e) { + // https://github.com/flutter/flutter/issues/69298 + if (e is CameraException) { + debugPrint("CAMERA ERROR: ${e.code} = ${e.description}"); + /*switch (e.code) { + case 'CameraAccessDenied': + break; + default: + break; + }*/ + } + }); + _controller?.addListener(() { + debugPrint("CAMERA NOTIFIER: ${_controller?.value}"); + }); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + Future captureImage() async { + if (_controller != null && _controller!.value.isInitialized) { + try { + final XFile? imageFile = await _controller?.takePicture(); + debugPrint("CAMERA IMAGE imageFile.path: ${imageFile?.path}"); + return imageFile; + } catch (e) { + debugPrint("CAMERA ERROR: $e"); + } + } + return null; + } + + void startRecording() async { + if (_controller != null && _controller!.value.isInitialized) { + try { + await _controller?.startVideoRecording(); + } catch (e) { + debugPrint("VIDEO ERROR starting video recording: $e"); + } + } + } + + Future stopRecording() async { + if (_controller != null && _controller!.value.isRecordingVideo) { + try { + final XFile? videoFile = await _controller?.stopVideoRecording(); + debugPrint("VIDEO recording stopped. File: ${videoFile?.path}"); + return videoFile; + } catch (e) { + debugPrint("VIDEO ERROR stopping video recording: $e"); + } + } + + return null; + } + + @override + Widget build(BuildContext context) { + bool disabled = widget.control.isDisabled || widget.parentDisabled; + var exposureMode = widget.control.attrString("exposureMode"); + var exposureOffset = widget.control.attrDouble("exposureOffset"); + var zoomLevel = widget.control.attrDouble("zoomLevel"); + + () async { + if (!kIsWeb && + exposureMode != null && + parseExposureMode(exposureMode) != null) { + await _controller?.setExposureMode(parseExposureMode(exposureMode)!); + } + + if (!kIsWeb && exposureOffset != null) { + await _controller?.setExposureOffset(exposureOffset); + } + + if (zoomLevel != null && false) { + // && 1.0 <= zoomLevel && zoomLevel <= _controller.getMaxZoomLevel() + await _controller?.setZoomLevel(zoomLevel); + } + + widget.backend.subscribeMethods(widget.control.id, + (methodName, args) async { + switch (methodName) { + case "capture_image": + debugPrint("CAMERA.captureImage()"); + await captureImage(); + break; + case "start_video_recording": + debugPrint("CAMERA.startRecording()"); + startRecording(); + return null; + case "stop_video_recording": + debugPrint("CAMERA.stopRecording()"); + var output = await stopRecording(); + return output?.path; + default: + debugPrint("CAMERA unknown method: $methodName"); + break; + } + return null; + }); + }(); + + var errorContentCtrls = + widget.children.where((c) => c.name == "error_content" && c.isVisible); + + var camera = + _controller != null && (_controller?.value.isInitialized ?? false) + ? CameraPreview(_controller!) + : errorContentCtrls.isNotEmpty + ? createControl( + widget.control, errorContentCtrls.first.id, disabled) + : const ErrorControl("Camera not initialized."); + + debugPrint("Camera build: ${widget.control.id}"); + + return constrainedControl(context, camera, widget.parent, widget.control); + } +} diff --git a/packages/flet_camera/lib/src/create_control.dart b/packages/flet_camera/lib/src/create_control.dart new file mode 100644 index 000000000..e25c8f706 --- /dev/null +++ b/packages/flet_camera/lib/src/create_control.dart @@ -0,0 +1,23 @@ +import 'package:flet/flet.dart'; + +import 'camera.dart'; + +CreateControlFactory createControl = (CreateControlArgs args) { + switch (args.control.type) { + case "camera": + return CameraControl( + key: args.key, + parent: args.parent, + control: args.control, + children: args.children, + parentDisabled: args.parentDisabled, + backend: args.backend, + ); + default: + return null; + } +}; + +Future ensureInitialized() async { + // nothing to initialize +} diff --git a/packages/flet_camera/lib/utils/camera.dart b/packages/flet_camera/lib/utils/camera.dart new file mode 100644 index 000000000..94c7f61af --- /dev/null +++ b/packages/flet_camera/lib/utils/camera.dart @@ -0,0 +1,32 @@ +import 'package:camera/camera.dart'; +import 'package:collection/collection.dart'; + +ResolutionPreset? parseResolutionPreset(String? resolutionPreset, + [ResolutionPreset? defaultValue]) { + if (resolutionPreset == null) { + return defaultValue; + } + return ResolutionPreset.values.firstWhereOrNull((e) => + e.toString().toLowerCase() == resolutionPreset.toLowerCase()) ?? + defaultValue; +} + +ImageFormatGroup? parseImageFormatGroup(String? imageFormatGroup, + [ImageFormatGroup? defaultValue]) { + if (imageFormatGroup == null) { + return defaultValue; + } + return ImageFormatGroup.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == imageFormatGroup.toLowerCase()) ?? + defaultValue; +} + +ExposureMode? parseExposureMode(String? resolutionPreset, + [ExposureMode? defaultValue]) { + if (resolutionPreset == null) { + return defaultValue; + } + return ExposureMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == resolutionPreset.toLowerCase()) ?? + defaultValue; +} diff --git a/packages/flet_camera/pubspec.yaml b/packages/flet_camera/pubspec.yaml new file mode 100644 index 000000000..7bb31e5f6 --- /dev/null +++ b/packages/flet_camera/pubspec.yaml @@ -0,0 +1,24 @@ +name: flet_camera +description: Flet Camera control +homepage: https://flet.dev +repository: https://github.com/flet-dev/flet/packages/flet_camera +version: 0.22.1 + +environment: + sdk: '>=3.2.3 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + collection: ^1.16.0 + camera: ^0.11.0 + + flet: + path: ../flet/ + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 \ No newline at end of file diff --git a/sdk/python/packages/flet-core/src/flet_core/__init__.py b/sdk/python/packages/flet-core/src/flet_core/__init__.py index c8a44eaca..5bde3b878 100644 --- a/sdk/python/packages/flet-core/src/flet_core/__init__.py +++ b/sdk/python/packages/flet-core/src/flet_core/__init__.py @@ -44,6 +44,7 @@ RoundedRectangleBorder, StadiumBorder, ) +from flet_core.camera import Camera, ExposureMode, ImageFormatGroup, ResolutionPreset from flet_core.card import Card, CardVariant from flet_core.charts.bar_chart import BarChart, BarChartEvent from flet_core.charts.bar_chart_group import BarChartGroup diff --git a/sdk/python/packages/flet-core/src/flet_core/camera.py b/sdk/python/packages/flet-core/src/flet_core/camera.py new file mode 100644 index 000000000..7c7999ba9 --- /dev/null +++ b/sdk/python/packages/flet-core/src/flet_core/camera.py @@ -0,0 +1,235 @@ +from enum import Enum +from typing import Any, Optional, Union + +from flet_core.constrained_control import ConstrainedControl +from flet_core.control import OptionalNumber, Control +from flet_core.ref import Ref +from flet_core.types import ( + AnimationValue, + OffsetValue, + ResponsiveNumber, + RotateValue, + ScaleValue, +) + + +class ResolutionPreset(Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + VERY_HIGH = "veryHigh" + ULTRA_HIGH = "ultraHigh" + MAX = "max" + + +class ImageFormatGroup(Enum): + UNKNOWN = "unknown" + YUV420 = "yuv420" + BGRA8888 = "bgra8888" + JPEG = "jpeg" + NV21 = "nv21" + + +class ExposureMode(Enum): + LOCKED = "locked" + AUTO = "auto" + + +class Camera(ConstrainedControl): + """ + Camera control. + + ----- + + Online docs: https://flet.dev/docs/controls/camera + """ + + def __init__( + self, + enable_audio: Optional[bool] = None, + resolution_preset: Optional[ResolutionPreset] = None, + image_format_group: Optional[ImageFormatGroup] = None, + exposure_mode: Optional[ExposureMode] = None, + exposure_offset: OptionalNumber = None, + zoom_level: OptionalNumber = None, + error_content: Optional[Control] = None, + on_camera_access_denied=None, + # + # ConstrainedControl + # + ref: Optional[Ref] = None, + key: Optional[str] = None, + width: OptionalNumber = None, + height: OptionalNumber = None, + left: OptionalNumber = None, + top: OptionalNumber = None, + right: OptionalNumber = None, + bottom: OptionalNumber = None, + expand: Union[None, bool, int] = None, + col: Optional[ResponsiveNumber] = None, + opacity: OptionalNumber = None, + rotate: RotateValue = None, + scale: ScaleValue = None, + offset: OffsetValue = None, + aspect_ratio: OptionalNumber = None, + animate_opacity: AnimationValue = None, + animate_size: AnimationValue = None, + animate_position: AnimationValue = None, + animate_rotation: AnimationValue = None, + animate_scale: AnimationValue = None, + animate_offset: AnimationValue = None, + on_animation_end=None, + tooltip: Optional[str] = None, + visible: Optional[bool] = None, + disabled: Optional[bool] = None, + data: Any = None, + ): + ConstrainedControl.__init__( + self, + ref=ref, + key=key, + width=width, + height=height, + left=left, + top=top, + right=right, + bottom=bottom, + expand=expand, + col=col, + opacity=opacity, + rotate=rotate, + scale=scale, + offset=offset, + aspect_ratio=aspect_ratio, + animate_opacity=animate_opacity, + animate_size=animate_size, + animate_position=animate_position, + animate_rotation=animate_rotation, + animate_scale=animate_scale, + animate_offset=animate_offset, + on_animation_end=on_animation_end, + tooltip=tooltip, + visible=visible, + disabled=disabled, + data=data, + ) + + self.enable_audio = enable_audio + self.resolution_preset = resolution_preset + self.image_format_group = image_format_group + self.exposure_mode = exposure_mode + self.exposure_offset = exposure_offset + self.zoom_level = zoom_level + self.error_content = error_content + self.on_camera_access_denied = on_camera_access_denied + + def _get_control_name(self): + return "camera" + + def _get_children(self): + children = [] + if self.__error_content is not None: + self.__error_content._set_attr_internal("n", "error_content") + children.append(self.__error_content) + return children + + def capture_image(self): + self.page.invoke_method("capture_image", control_id=self.uid) + + async def capture_image_async(self): + await self.page.invoke_method_async("capture_image", control_id=self.uid) + + def start_video_recording(self): + self.page.invoke_method("start_video_recording", control_id=self.uid) + + def stop_video_recording(self, wait_timeout: Optional[int] = 5): + return self.page.invoke_method( + "stop_video_recording", + control_id=self.uid, + wait_for_result=True, + wait_timeout=wait_timeout, + ) + + async def stop_video_recording_async(self, wait_timeout: Optional[int] = 5): + return await self.page.invoke_method_async( + "stop_video_recording", + control_id=self.uid, + wait_for_result=True, + wait_timeout=wait_timeout, + ) + + # enable_audio + @property + def enable_audio(self) -> Optional[bool]: + return self._get_attr("enableAudio") + + @enable_audio.setter + def enable_audio(self, value: Optional[bool]): + self._set_attr("enableAudio", value) + + # resolution_preset + @property + def resolution_preset(self) -> Optional[ResolutionPreset]: + return self.__resolution_preset + + @resolution_preset.setter + def resolution_preset(self, value: Optional[ResolutionPreset]): + self.__resolution_preset = value + self._set_enum_attr("resolutionPreset", value, ResolutionPreset) + + # image_format_group + @property + def image_format_group(self) -> Optional[ImageFormatGroup]: + return self.__image_format_group + + @image_format_group.setter + def image_format_group(self, value: Optional[ImageFormatGroup]): + self.__image_format_group = value + self._set_enum_attr("imageFormatGroup", value, ImageFormatGroup) + + # exposure_mode + @property + def exposure_mode(self) -> Optional[ExposureMode]: + return self.__exposure_mode + + @exposure_mode.setter + def exposure_mode(self, value: Optional[ExposureMode]): + self.__exposure_mode = value + self._set_enum_attr("exposureMode", value, ExposureMode) + + # exposure_offset + @property + def exposure_offset(self) -> OptionalNumber: + return self._get_attr("exposureOffset", data_type="float") + + @exposure_offset.setter + def exposure_offset(self, value: OptionalNumber): + self._set_attr("exposureOffset", value) + + # zoom_level + @property + def zoom_level(self) -> OptionalNumber: + return self._get_attr("zoomLevel", data_type="float") + + @zoom_level.setter + def zoom_level(self, value: OptionalNumber): + self._set_attr("zoomLevel", value) + + # error_content + @property + def error_content(self) -> Optional[Control]: + return self.__error_content + + @error_content.setter + def error_content(self, value: Optional[Control]): + self.__error_content = value + + # on_camera_access_denied + @property + def on_camera_access_denied(self): + return self._get_event_handler("cameraAccessDenied") + + @on_camera_access_denied.setter + def on_camera_access_denied(self, handler): + self._add_event_handler("cameraAccessDenied", handler) + self._set_attr("onCameraAccessDenied", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/cli/commands/build.py b/sdk/python/packages/flet/src/flet/cli/commands/build.py index dd5a3753e..4d564f84f 100644 --- a/sdk/python/packages/flet/src/flet/cli/commands/build.py +++ b/sdk/python/packages/flet/src/flet/cli/commands/build.py @@ -12,14 +12,15 @@ from pathlib import Path from typing import Optional -import flet.version import yaml +from packaging import version +from rich import print + +import flet.version from flet.cli.commands.base import BaseCommand from flet.version import update_version from flet_core.utils import random_string, slugify from flet_runtime.utils import calculate_file_hash, copy_tree, is_windows -from packaging import version -from rich import print if is_windows(): from ctypes import windll @@ -335,8 +336,7 @@ def handle(self, options: argparse.Namespace) -> None: else python_app_path.joinpath(rel_out_dir) ) - template_data = {} - template_data["out_dir"] = self.flutter_dir.name + template_data = {"out_dir": self.flutter_dir.name} project_name = slugify( options.project_name if options.project_name else python_app_path.name