diff --git a/.appveyor.yml b/.appveyor.yml index bfc186566e..e65346b712 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -58,11 +58,11 @@ environment: job_depends_on: build_flet_package APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu - # - job_name: Test Python 3.7 - # job_group: python_tests - # job_depends_on: build_flet - # python_stack: python 3.7 - # APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu + - job_name: Test Python 3.7 + job_group: python_tests + job_depends_on: build_flet + python_stack: python 3.7 + APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu - job_name: Test Python 3.8 job_group: python_tests @@ -144,7 +144,8 @@ for: build_script: - cd client - - flutter build windows + - ps: if ($env:APPVEYOR_REPO_TAG_NAME -match "[0-9\.]+") { $env:FLET_VER=$Matches.0 } else { $env:FLET_VER=$env:APPVEYOR_BUILD_VERSION } + - flutter build windows --build-name=%FLET_VER% - set RELEASE_DIR=build\windows\runner\Release - copy "%VC_REDIST_DIR%\msvcp140.dll" %RELEASE_DIR% - copy "%VC_REDIST_DIR%\vcruntime140.dll" %RELEASE_DIR% @@ -190,7 +191,8 @@ for: build_script: # Flutter macOS client - cd client - - flutter build macos + - if [[ "$APPVEYOR_REPO_TAG_NAME" =~ ([0-9\.]+) ]]; then export FLET_VER="${BASH_REMATCH[1]}"; else export FLET_VER="$APPVEYOR_BUILD_VERSION"; fi + - flutter build macos --build-name=$FLET_VER - tar -czvf flet-macos-amd64.tar.gz -C build/macos/Build/Products/Release Flet.app artifacts: @@ -226,7 +228,8 @@ for: build_script: - cd client - - flutter build linux + - if [[ "$APPVEYOR_REPO_TAG_NAME" =~ ([0-9\.]+) ]]; then export FLET_VER="${BASH_REMATCH[1]}"; else export FLET_VER="$APPVEYOR_BUILD_VERSION"; fi + - flutter build linux --build-name=$FLET_VER - mv build/linux/x64/release/bundle build/linux/x64/release/flet - tar -czvf flet-linux-amd64.tar.gz -C build/linux/x64/release flet @@ -263,7 +266,8 @@ for: build_script: - cd client - - flutter build linux + - if [[ "$APPVEYOR_REPO_TAG_NAME" =~ ([0-9\.]+) ]]; then export FLET_VER="${BASH_REMATCH[1]}"; else export FLET_VER="$APPVEYOR_BUILD_VERSION"; fi + - flutter build linux --build-name=$FLET_VER - mv build/linux/arm64/release/bundle build/linux/arm64/release/flet - tar -czvf flet-linux-arm64.tar.gz -C build/linux/arm64/release flet @@ -433,7 +437,7 @@ for: # patch version $env:PACKAGE_VERSION = $ver (Get-Content pyproject.toml).replace("version = `"0.1.0`"", "version = `"$ver`"") | Set-Content pyproject.toml - ("# this file was auto-generated by CI", "version = '$ver'") | Set-Content flet/version.py + (Get-Content flet/version.py).replace("version = `"`"", "version = `"$ver`"") | Set-Content flet/version.py # build package - pdm build diff --git a/client/integration_test/app_test.dart b/client/integration_test/app_test.dart new file mode 100644 index 0000000000..b9508b3526 --- /dev/null +++ b/client/integration_test/app_test.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +import 'package:flet_client/main.dart' as app; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + tearDown(() { + debugPrint("TEAR DOWN"); + }); + + group('end-to-end test', () { + testWidgets('tap on the floating action button, verify counter', + (tester) async { + var dir = Directory.current.path; + debugPrint("Current dir: $dir"); + app.main(); + await tester.pumpAndSettle(const Duration(milliseconds: 100), + EnginePhase.sendSemanticsUpdate, const Duration(seconds: 20)); + + // Verify the counter starts at 0. + expect(find.text('0'), findsOneWidget); + + // Finds the floating action button to tap on. + final Finder fab = find.byTooltip('Increment'); + + // Emulate a tap on the floating action button. + await tester.tap(fab); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the counter increments by 1. + expect(find.text('1'), findsOneWidget); + }); + }); +} diff --git a/client/lib/main.dart b/client/lib/main.dart index 2d5013c960..b566547792 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -41,5 +41,22 @@ void main([List? args]) async { debugPrint("Page URL: $pageUrl"); - runApp(FletApp(title: 'Flet', pageUrl: pageUrl)); + FletAppErrorsHandler errorsHandler = FletAppErrorsHandler(); + + if (!kDebugMode) { + FlutterError.onError = (details) { + errorsHandler.onError(details.exceptionAsString()); + }; + + PlatformDispatcher.instance.onError = (error, stack) { + errorsHandler.onError(error.toString()); + return true; + }; + } + + runApp(FletApp( + title: 'Flet', + pageUrl: pageUrl, + errorsHandler: errorsHandler, + )); } diff --git a/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e879bfaa2..613e0d8a99 100644 --- a/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/client/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index ccc986b603..cd0e222677 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 9161c1d45a..a7f8559d3c 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index cff7499b82..e6d5260711 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index d260583bff..117ff24147 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 8dfc9c40b9..541cd5d605 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 5dd213ba9b..f89a749ada 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 0228be82bc..2a05709be4 100644 Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/client/pubspec.lock b/client/pubspec.lock index 3d728fdfd5..7682dafd31 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -85,13 +85,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" clock: dependency: transitive description: @@ -112,7 +105,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" equatable: dependency: transitive description: @@ -140,7 +133,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.4" + version: "6.1.2" file_picker: dependency: transitive description: @@ -160,6 +153,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_launcher_icons_maker: dependency: "direct dev" description: @@ -180,7 +178,7 @@ packages: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.6.10+3" + version: "0.6.12" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -212,6 +210,11 @@ packages: description: flutter source: sdk version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: transitive description: @@ -233,6 +236,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.2.0" + integration_test: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" js: dependency: transitive description: @@ -253,7 +261,7 @@ packages: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "6.0.1" matcher: dependency: transitive description: @@ -267,7 +275,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.1.5" meta: dependency: transitive description: @@ -454,7 +462,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.9.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -476,6 +484,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -489,14 +504,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.14" + version: "0.4.12" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" url_launcher: dependency: transitive description: @@ -573,7 +588,14 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.0" web_socket_channel: dependency: transitive description: @@ -581,6 +603,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" win32: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 4f9cec0955..8eefb4ccf8 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -34,6 +34,8 @@ dependencies: path: ../package/ url_strategy: ^0.2.0 + integration_test: + sdk: flutter dev_dependencies: flutter_test: diff --git a/package/CHANGELOG.md b/package/CHANGELOG.md index 7307a324a7..fbe1f6a421 100644 --- a/package/CHANGELOG.md +++ b/package/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.0.1 +## 0.1.62 * Initial release of Flet package. \ No newline at end of file diff --git a/package/README.md b/package/README.md index 02fe8ecabc..d3e2e5665b 100644 --- a/package/README.md +++ b/package/README.md @@ -1,39 +1,18 @@ - +Provides an interactive Flutter widget which contents is controlled by a remote Python script running on a web server. -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +Flet provides a very approachable Python library, so developers without prior Flutter experience can easily build parts of your bigger Flutter app or develop their own mobile apps from scratch. -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. +Flet implements Server-Driven UI (SDUI) approach reducing application release cycle. ## Usage TODO: Include short and useful examples for package users. Add longer examples + to `/example` folder. ```dart const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +``` \ No newline at end of file diff --git a/package/lib/flet.dart b/package/lib/flet.dart index 132c168b76..51af540b10 100644 --- a/package/lib/flet.dart +++ b/package/lib/flet.dart @@ -1,6 +1,7 @@ library flet; export 'src/flet_app.dart'; +export 'src/flet_app_errors_handler.dart'; export 'src/utils.dart'; export 'src/utils/platform_utils_non_web.dart' if (dart.library.js) "src/utils/platform_utils_web.dart"; diff --git a/package/lib/src/controls/app_bar.dart b/package/lib/src/controls/app_bar.dart index 4cb0eb4386..9214f60241 100644 --- a/package/lib/src/controls/app_bar.dart +++ b/package/lib/src/controls/app_bar.dart @@ -32,6 +32,8 @@ class AppBarControl extends StatelessWidget implements PreferredSizeWidget { var leadingWidth = control.attrDouble("leadingWidth"); var elevation = control.attrDouble("elevation"); var centerTitle = control.attrBool("centerTitle", false)!; + var automaticallyImplyLeading = + control.attrBool("automaticallyImplyLeading", true)!; var color = HexColor.fromString( Theme.of(context), control.attrString("color", "")!); var bgcolor = HexColor.fromString( @@ -42,6 +44,7 @@ class AppBarControl extends StatelessWidget implements PreferredSizeWidget { ? createControl(control, leadingCtrls.first.id, control.isDisabled) : null, leadingWidth: leadingWidth, + automaticallyImplyLeading: automaticallyImplyLeading, title: titleCtrls.isNotEmpty ? createControl(control, titleCtrls.first.id, control.isDisabled) : null, diff --git a/package/lib/src/controls/audio.dart b/package/lib/src/controls/audio.dart index 91e6a88c3b..9a872d0b36 100644 --- a/package/lib/src/controls/audio.dart +++ b/package/lib/src/controls/audio.dart @@ -26,6 +26,7 @@ class _AudioControlState extends State { ReleaseMode? _releaseMode; double? _volume; double? _balance; + double? _playbackRate; String? _method; void Function(Duration)? _onDurationChanged; void Function(PlayerState)? _onStateChanged; @@ -81,6 +82,7 @@ class _AudioControlState extends State { bool autoplay = widget.control.attrBool("autoplay", false)!; double? volume = widget.control.attrDouble("volume", null); double? balance = widget.control.attrDouble("balance", null); + double? playbackRate = widget.control.attrDouble("playbackRate", null); var releaseMode = ReleaseMode.values.firstWhereOrNull((e) => e.name.toLowerCase() == widget.control.attrString("releaseMode", "")!.toLowerCase()); @@ -146,6 +148,14 @@ class _AudioControlState extends State { await player.setVolume(volume); } + if (playbackRate != null && + playbackRate != _playbackRate && + playbackRate >= 0 && + playbackRate <= 2) { + _playbackRate = playbackRate; + await player.setPlaybackRate(playbackRate); + } + if (!kIsWeb && balance != null && balance != _balance && diff --git a/package/lib/src/controls/checkbox.dart b/package/lib/src/controls/checkbox.dart index 8799d154df..435289cf8a 100644 --- a/package/lib/src/controls/checkbox.dart +++ b/package/lib/src/controls/checkbox.dart @@ -6,6 +6,8 @@ import '../flet_app_services.dart'; import '../models/app_state.dart'; import '../models/control.dart'; import '../protocol/update_control_props_payload.dart'; +import '../utils/buttons.dart'; +import '../utils/colors.dart'; import 'create_control.dart'; enum LabelPosition { right, left } @@ -100,6 +102,10 @@ class _CheckboxControlState extends State { autofocus: autofocus, focusNode: _focusNode, value: _value, + checkColor: HexColor.fromString(Theme.of(context), + widget.control.attrString("checkColor", "")!), + fillColor: parseMaterialStateColor( + Theme.of(context), widget.control, "fillColor"), tristate: tristate, onChanged: !disabled ? (bool? value) { diff --git a/package/lib/src/controls/clipboard.dart b/package/lib/src/controls/clipboard.dart new file mode 100644 index 0000000000..776b1ae7cf --- /dev/null +++ b/package/lib/src/controls/clipboard.dart @@ -0,0 +1,79 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../actions.dart'; +import '../flet_app_services.dart'; +import '../models/control.dart'; +import '../protocol/update_control_props_payload.dart'; + +class ClipboardControl extends StatefulWidget { + final Control? parent; + final Control control; + + const ClipboardControl( + {Key? key, required this.parent, required this.control}) + : super(key: key); + + @override + State createState() => _ClipboardControlState(); +} + +class _ClipboardControlState extends State { + String? _method; + + @override + Widget build(BuildContext context) { + debugPrint("Clipboard build: ${widget.control.id}"); + + () async { + var method = widget.control.attrString("method"); + if (method != null && method != _method) { + _method = method; + debugPrint("Clipboard JSON value: $_method"); + + List> props = [ + {"i": widget.control.id, "method": ""} + ]; + FletAppServices.of(context).store.dispatch( + UpdateControlPropsAction(UpdateControlPropsPayload(props: props))); + FletAppServices.of(context).ws.updateControlProps(props: props); + + var mj = json.decode(method); + var i = mj["i"] as int; + var name = mj["n"] as String; + var params = List.from(mj["p"] as List); + + sendResult(Object? result, String? error) { + FletAppServices.of(context).ws.pageEventFromWeb( + eventTarget: widget.control.id, + eventName: "method_result", + eventData: json.encode({ + "i": i, + "r": result != null ? json.encode(result) : null, + "e": error + })); + } + + switch (name) { + case "set_data": + Clipboard.setData(ClipboardData(text: params[0])); + break; + case "get_data": + String? r; + String? ex; + try { + r = (await Clipboard.getData(Clipboard.kTextPlain))?.text; + } catch (e) { + ex = e.toString(); + } + sendResult(r, ex); + break; + } + } + }(); + + return const SizedBox.shrink(); + } +} diff --git a/package/lib/src/controls/container.dart b/package/lib/src/controls/container.dart index 1c1f06b779..0f18b8edef 100644 --- a/package/lib/src/controls/container.dart +++ b/package/lib/src/controls/container.dart @@ -99,6 +99,11 @@ class ContainerControl extends StatelessWidget { var blendMode = BlendMode.values.firstWhereOrNull((e) => e.name.toLowerCase() == control.attrString("blendMode", "")!.toLowerCase()); + var shape = BoxShape.values.firstWhere( + (e) => + e.name.toLowerCase() == + control.attrString("shape", "")!.toLowerCase(), + orElse: () => BoxShape.rectangle); var boxDecor = BoxDecoration( color: bgColor, @@ -107,7 +112,8 @@ class ContainerControl extends StatelessWidget { backgroundBlendMode: bgColor != null || gradient != null ? blendMode : null, border: parseBorder(Theme.of(context), control, "border"), - borderRadius: parseBorderRadius(control, "borderRadius")); + borderRadius: parseBorderRadius(control, "borderRadius"), + shape: shape); if ((onClick || onLongPress || onHover) && ink && !disabled) { var ink = Ink( @@ -116,9 +122,7 @@ class ContainerControl extends StatelessWidget { // Dummy callback to enable widget // see https://github.com/flutter/flutter/issues/50116#issuecomment-582047374 // and https://github.com/flutter/flutter/blob/eed80afe2c641fb14b82a22279d2d78c19661787/packages/flutter/lib/src/material/ink_well.dart#L1125-L1129 - onTap: onHover - ? () {} - : null, + onTap: onHover ? () {} : null, onTapDown: onClick ? (details) { debugPrint("Container ${control.id} clicked!"); diff --git a/package/lib/src/controls/create_control.dart b/package/lib/src/controls/create_control.dart index 62c36c2324..00bfe33200 100644 --- a/package/lib/src/controls/create_control.dart +++ b/package/lib/src/controls/create_control.dart @@ -1,12 +1,12 @@ import 'dart:math'; +import 'package:flet/src/controls/error.dart'; import 'package:flet/src/flet_app_services.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import '../models/app_state.dart'; import '../models/control.dart'; -import '../models/control_type.dart'; import '../models/control_view_model.dart'; import '../utils/animations.dart'; import '../utils/transforms.dart'; @@ -17,6 +17,7 @@ import 'banner.dart'; import 'card.dart'; import 'checkbox.dart'; import 'circle_avatar.dart'; +import 'clipboard.dart'; import 'column.dart'; import 'container.dart'; import 'divider.dart'; @@ -26,6 +27,7 @@ import 'dropdown.dart'; import 'elevated_button.dart'; import 'file_picker.dart'; import 'floating_action_button.dart'; +import 'gesture_detector.dart'; import 'grid_view.dart'; import 'icon.dart'; import 'icon_button.dart'; @@ -74,223 +76,231 @@ Widget createControl(Control? parent, String id, bool parentDisabled) { builder: (context, controlView) { //debugPrint("createControl builder(): $id"); switch (controlView.control.type) { - case ControlType.page: + case "page": return PageControl( control: controlView.control, children: controlView.children, dispatch: controlView.dispatch); - case ControlType.text: + case "text": return TextControl(parent: parent, control: controlView.control); - case ControlType.icon: + case "icon": return IconControl(parent: parent, control: controlView.control); - case ControlType.filePicker: + case "filepicker": return FilePickerControl( parent: parent, control: controlView.control); - case ControlType.markdown: + case "markdown": return MarkdownControl(parent: parent, control: controlView.control); - case ControlType.image: + case "image": return ImageControl(parent: parent, control: controlView.control); - case ControlType.audio: + case "audio": return AudioControl(parent: parent, control: controlView.control); - case ControlType.divider: + case "divider": return DividerControl(parent: parent, control: controlView.control); - case ControlType.verticalDivider: + case "clipboard": + return ClipboardControl(parent: parent, control: controlView.control); + case "verticaldivider": return VerticalDividerControl( parent: parent, control: controlView.control); - case ControlType.circleAvatar: + case "circleavatar": return CircleAvatarControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.progressRing: + case "progressring": return ProgressRingControl( parent: parent, control: controlView.control); - case ControlType.progressBar: + case "progressbar": return ProgressBarControl( parent: parent, control: controlView.control); - case ControlType.elevatedButton: + case "elevatedbutton": return ElevatedButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.outlinedButton: + case "outlinedbutton": return OutlinedButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.textButton: + case "textbutton": return TextButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.iconButton: + case "iconbutton": return IconButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.floatingActionButton: + case "floatingactionbutton": return FloatingActionButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.popupMenuButton: + case "popupmenubutton": return PopupMenuButtonControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.column: + case "column": return ColumnControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.row: + case "row": return RowControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.stack: + case "stack": return StackControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.container: + case "container": return ContainerControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.draggable: + case "draggable": return DraggableControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.dragTarget: + case "dragtarget": return DragTargetControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.card: + case "card": return CardControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.semantics: + case "gesturedetector": + return GestureDetectorControl( + parent: parent, + control: controlView.control, + children: controlView.children, + parentDisabled: parentDisabled); + case "semantics": return SemanticsControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.shaderMask: + case "shadermask": return ShaderMaskControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.animatedSwitcher: + case "animatedswitcher": return AnimatedSwitcherControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.listTile: + case "listtile": return ListTileControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.listView: + case "listview": return ListViewControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.gridView: + case "gridview": return GridViewControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.textField: + case "textfield": return TextFieldControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.checkbox: + case "checkbox": return CheckboxControl( parent: parent, control: controlView.control, parentDisabled: parentDisabled); - case ControlType.Switch: + case "switch": return SwitchControl( parent: parent, control: controlView.control, parentDisabled: parentDisabled); - case ControlType.slider: + case "slider": return SliderControl( parent: parent, control: controlView.control, parentDisabled: parentDisabled); - case ControlType.radioGroup: + case "radiogroup": return RadioGroupControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.radio: + case "radio": return RadioControl( parent: parent, control: controlView.control, parentDisabled: parentDisabled); - case ControlType.dropdown: + case "dropdown": return DropdownControl( parent: parent, control: controlView.control, parentDisabled: parentDisabled); - case ControlType.snackBar: + case "snackbar": return SnackBarControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.alertDialog: + case "alertdialog": return AlertDialogControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.banner: + case "banner": return BannerControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.tabs: + case "tabs": return TabsControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.navigationRail: + case "navigationrail": return NavigationRailControl( parent: parent, control: controlView.control, children: controlView.children, parentDisabled: parentDisabled); - case ControlType.windowDragArea: + case "windowdragarea": return WindowDragAreaControl( parent: parent, control: controlView.control, @@ -370,11 +380,8 @@ Widget _opacity( Widget _tooltip(Widget widget, Control? parent, Control control) { var tooltip = control.attrString("tooltip"); return tooltip != null && - ![ - ControlType.iconButton, - ControlType.floatingActionButton, - ControlType.popupMenuButton - ].contains(control.type) + !["iconbutton", "floatingactionbutton", "popupmenubutton"] + .contains(control.type) ? Tooltip( message: tooltip, padding: const EdgeInsets.all(4.0), @@ -496,6 +503,11 @@ Widget _positionedControl( child: widget, ); } else if (left != null || top != null || right != null || bottom != null) { + if (parent?.type != "stack" && parent?.type != "page") { + return ErrorControl("Error displaying ${control.type}", + description: + "Control can be positioned absolutely with \"left\", \"top\", \"right\" and \"bottom\" properties inside Stack control only."); + } return Positioned( left: left, top: top, @@ -511,8 +523,7 @@ Widget _sizedControl(Widget widget, Control? parent, Control control) { var width = control.attrDouble("width", null); var height = control.attrDouble("height", null); if (width != null || height != null) { - if (control.type != ControlType.container && - control.type != ControlType.image) { + if (control.type != "container" && control.type != "image") { widget = ConstrainedBox( constraints: BoxConstraints.tightFor(width: width, height: height), child: widget, @@ -529,9 +540,9 @@ Widget _sizedControl(Widget widget, Control? parent, Control control) { Widget _expandable(Widget widget, Control? parent, Control control) { if (parent != null && - (parent.type == ControlType.view || - parent.type == ControlType.column || - parent.type == ControlType.row)) { + (parent.type == "view" || + parent.type == "column" || + parent.type == "row")) { //debugPrint("Expandable ${control.id}"); int? expand = control.attrInt("expand"); return expand != null ? Expanded(flex: expand, child: widget) : widget; diff --git a/package/lib/src/controls/drag_target.dart b/package/lib/src/controls/drag_target.dart index 8470ff3166..87cff265b6 100644 --- a/package/lib/src/controls/drag_target.dart +++ b/package/lib/src/controls/drag_target.dart @@ -1,11 +1,12 @@ import 'dart:convert'; -import 'error.dart'; import 'package:flutter/material.dart'; import '../flet_app_services.dart'; import '../models/control.dart'; +import '../protocol/drag_target_accept_event.dart'; import 'create_control.dart'; +import 'error.dart'; class DragTargetControl extends StatelessWidget { final Control? parent; @@ -64,17 +65,18 @@ class DragTargetControl extends StatelessWidget { eventData: groupsEqual.toString()); return groupsEqual; }, - onAccept: (data) { - debugPrint("DragTarget.onAccept ${control.id}: $data"); + onAcceptWithDetails: (details) { + var data = details.data; + debugPrint("DragTarget.onAcceptWithDetails ${control.id}: $data"); var jd = json.decode(data); var srcId = jd["id"] as String; ws.pageEventFromWeb( - eventTarget: control.id, eventName: "accept", eventData: srcId); + eventTarget: control.id, + eventName: "accept", + eventData: json.encode(DragTargetAcceptEvent( + srcId: srcId, x: details.offset.dx, y: details.offset.dy) + .toJson())); }, - // onAcceptWithDetails: (details) { - // debugPrint( - // "onAcceptWithDetails: ${details.data} ${details.offset}"); - // }, onLeave: (data) { debugPrint("DragTarget.onLeave ${control.id}: $data"); String srcId = ""; diff --git a/package/lib/src/controls/elevated_button.dart b/package/lib/src/controls/elevated_button.dart index 4f94204c38..9050286b8c 100644 --- a/package/lib/src/controls/elevated_button.dart +++ b/package/lib/src/controls/elevated_button.dart @@ -1,3 +1,4 @@ +import 'package:flet/src/controls/error.dart'; import 'package:flutter/material.dart'; import '../flet_app_services.dart'; @@ -82,6 +83,10 @@ class ElevatedButtonControl extends StatelessWidget { : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); if (icon != null) { + if (text == "") { + return const ErrorControl("Error displaying ElevatedButton", + description: "\"icon\" must be specified together with \"text\"."); + } button = ElevatedButton.icon( style: style, autofocus: autofocus, diff --git a/package/lib/src/controls/error.dart b/package/lib/src/controls/error.dart index 0a19832e28..77833b131f 100644 --- a/package/lib/src/controls/error.dart +++ b/package/lib/src/controls/error.dart @@ -2,17 +2,33 @@ import 'package:flutter/material.dart'; class ErrorControl extends StatelessWidget { final String message; + final String? description; - const ErrorControl(this.message, {Key? key}) : super(key: key); + const ErrorControl(this.message, {Key? key, this.description}) + : super(key: key); @override Widget build(BuildContext context) { debugPrint("Error build"); - return Container( + List lines = [ + Text(message, style: const TextStyle(color: Colors.white, fontSize: 12)) + ]; + if (description != null) { + lines.addAll([ + const SizedBox(height: 5), + Text(description!, + style: const TextStyle(color: Colors.white70, fontSize: 11)) + ]); + } + return SelectionArea( + child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(3)), - child: Text(message, style: const TextStyle(color: Colors.white)), - ); + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: lines, + ), + )); } } diff --git a/package/lib/src/controls/form_field.dart b/package/lib/src/controls/form_field.dart index 60793d8394..4f2eb3af33 100644 --- a/package/lib/src/controls/form_field.dart +++ b/package/lib/src/controls/form_field.dart @@ -1,3 +1,4 @@ +import 'package:flet/src/utils/text.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; @@ -107,6 +108,7 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, return InputDecoration( contentPadding: parseEdgeInsets(control, "contentPadding"), label: label != "" ? Text(label) : null, + labelStyle: parseTextStyle(Theme.of(context), control, "labelStyle"), border: border, enabledBorder: border, focusedBorder: focusedBorder, @@ -114,11 +116,16 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, filled: control.attrBool("filled", false)!, fillColor: focused ? focusedBgcolor ?? bgcolor : bgcolor, hintText: control.attrString("hintText"), + hintStyle: parseTextStyle(Theme.of(context), control, "hintStyle"), helperText: control.attrString("helperText"), + helperStyle: parseTextStyle(Theme.of(context), control, "helperStyle"), counterText: control.attrString("counterText"), + counterStyle: parseTextStyle(Theme.of(context), control, "counterStyle"), errorText: control.attrString("errorText"), + errorStyle: parseTextStyle(Theme.of(context), control, "errorStyle"), prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null, prefixText: prefixText, + prefixStyle: parseTextStyle(Theme.of(context), control, "prefixStyle"), prefix: prefix != null ? createControl(control, prefix.id, control.isDisabled) : null, @@ -126,5 +133,6 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, ? createControl(control, suffix.id, control.isDisabled) : null, suffixIcon: suffixIcon != null ? Icon(suffixIcon) : customSuffix, - suffixText: suffixText); + suffixText: suffixText, + suffixStyle: parseTextStyle(Theme.of(context), control, "suffixStyle")); } diff --git a/package/lib/src/controls/gesture_detector.dart b/package/lib/src/controls/gesture_detector.dart new file mode 100644 index 0000000000..e68576589f --- /dev/null +++ b/package/lib/src/controls/gesture_detector.dart @@ -0,0 +1,598 @@ +import 'dart:convert'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import '../flet_app_services.dart'; +import '../models/control.dart'; +import 'create_control.dart'; +import 'error.dart'; + +class GestureDetectorControl extends StatefulWidget { + final Control? parent; + final Control control; + final List children; + final bool parentDisabled; + + const GestureDetectorControl( + {Key? key, + this.parent, + required this.control, + required this.children, + required this.parentDisabled}) + : super(key: key); + + @override + State createState() => _GestureDetectorControlState(); +} + +class _GestureDetectorControlState extends State { + int _panTimestamp = DateTime.now().millisecondsSinceEpoch; + double _panX = 0; + double _panY = 0; + int _hDragTimestamp = DateTime.now().millisecondsSinceEpoch; + double _hDragX = 0; + double _hDragY = 0; + int _vDragTimestamp = DateTime.now().millisecondsSinceEpoch; + double _vDragX = 0; + double _vDragY = 0; + int _hoverTimestamp = DateTime.now().millisecondsSinceEpoch; + double _hoverX = 0; + double _hoverY = 0; + + @override + Widget build(BuildContext context) { + debugPrint("GestureDetector build: ${widget.control.id}"); + + var contentCtrls = + widget.children.where((c) => c.name == "content" && c.isVisible); + bool disabled = widget.control.isDisabled || widget.parentDisabled; + + var ws = FletAppServices.of(context).ws; + + void sendEvent(String eventName, dynamic eventData) { + var d = ""; + if (eventData is String) { + d = eventData; + } else if (eventData is Map) { + d = json.encode(eventData); + } + + debugPrint("GestureDetector ${widget.control.id} $eventName"); + ws.pageEventFromWeb( + eventTarget: widget.control.id, eventName: eventName, eventData: d); + } + + var onHover = widget.control.attrBool("onHover", false)!; + var onEnter = widget.control.attrBool("onEnter", false)!; + var onExit = widget.control.attrBool("onExit", false)!; + var onTap = widget.control.attrBool("onTap", false)!; + var onTapDown = widget.control.attrBool("onTapDown", false)!; + var onTapUp = widget.control.attrBool("onTapUp", false)!; + var onSecondaryTap = widget.control.attrBool("onSecondaryTap", false)!; + var onSecondaryTapDown = + widget.control.attrBool("onSecondaryTapDown", false)!; + var onSecondaryTapUp = widget.control.attrBool("onSecondaryTapUp", false)!; + var onLongPressStart = widget.control.attrBool("onLongPressStart", false)!; + var onLongPressEnd = widget.control.attrBool("onLongPressEnd", false)!; + var onSecondaryLongPressStart = + widget.control.attrBool("onSecondaryLongPressStart", false)!; + var onSecondaryLongPressEnd = + widget.control.attrBool("onSecondaryLongPressEnd", false)!; + var onDoubleTap = widget.control.attrBool("onDoubleTap", false)!; + var onDoubleTapDown = widget.control.attrBool("onDoubleTapDown", false)!; + var onHorizontalDragStart = + widget.control.attrBool("onHorizontalDragStart", false)!; + var onHorizontalDragUpdate = + widget.control.attrBool("onHorizontalDragUpdate", false)!; + var onHorizontalDragEnd = + widget.control.attrBool("onHorizontalDragEnd", false)!; + var onVerticalDragStart = + widget.control.attrBool("onVerticalDragStart", false)!; + var onVerticalDragUpdate = + widget.control.attrBool("onVerticalDragUpdate", false)!; + var onVerticalDragEnd = + widget.control.attrBool("onVerticalDragEnd", false)!; + var onPanStart = widget.control.attrBool("onPanStart", false)!; + var onPanUpdate = widget.control.attrBool("onPanUpdate", false)!; + var onPanEnd = widget.control.attrBool("onPanEnd", false)!; + var onScaleStart = widget.control.attrBool("onScaleStart", false)!; + var onScaleUpdate = widget.control.attrBool("onScaleUpdate", false)!; + var onScaleEnd = widget.control.attrBool("onScaleEnd", false)!; + + var content = contentCtrls.isNotEmpty + ? createControl(widget.control, contentCtrls.first.id, disabled) + : null; + + Widget? result; + + var dragInterval = widget.control.attrInt("dragInterval", 0)!; + + void handlePanStart(DragStartDetails details) { + _panX = details.localPosition.dx; + _panY = details.localPosition.dy; + if (onPanStart) { + sendEvent("pan_start", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + void handlePanUpdate(DragUpdateDetails details) { + var now = DateTime.now().millisecondsSinceEpoch; + if (now - _panTimestamp > dragInterval) { + _panTimestamp = now; + var dx = details.localPosition.dx - _panX; + var dy = details.localPosition.dy - _panY; + _panX = details.localPosition.dx; + _panY = details.localPosition.dy; + sendEvent("pan_update", { + "dx": dx, + "dy": dy, + "pd": details.primaryDelta, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + void handleHorizontalDragStart(DragStartDetails details) { + _hDragX = details.localPosition.dx; + _hDragY = details.localPosition.dy; + if (onHorizontalDragStart) { + sendEvent("horizontal_drag_start", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + void handleHorizontalDragUpdate(DragUpdateDetails details) { + var now = DateTime.now().millisecondsSinceEpoch; + if (now - _hDragTimestamp > dragInterval) { + _hDragTimestamp = now; + var dx = details.localPosition.dx - _hDragX; + var dy = details.localPosition.dy - _hDragY; + _hDragX = details.localPosition.dx; + _hDragY = details.localPosition.dy; + sendEvent("horizontal_drag_update", { + "dx": dx, + "dy": dy, + "pd": details.primaryDelta, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + void handleVerticalDragStart(DragStartDetails details) { + _vDragX = details.localPosition.dx; + _vDragY = details.localPosition.dy; + if (onVerticalDragStart) { + sendEvent("vertical_drag_start", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + void handleVerticalDragUpdate(DragUpdateDetails details) { + var now = DateTime.now().millisecondsSinceEpoch; + if (now - _vDragTimestamp > dragInterval) { + _vDragTimestamp = now; + var dx = details.localPosition.dx - _vDragX; + var dy = details.localPosition.dy - _vDragY; + _vDragX = details.localPosition.dx; + _vDragY = details.localPosition.dy; + sendEvent("vertical_drag_update", { + "dx": dx, + "dy": dy, + "pd": details.primaryDelta, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "ts": details.sourceTimeStamp?.inMilliseconds + }); + } + } + + var hoverInterval = widget.control.attrInt("hoverInterval", 0)!; + + void handleEnter(PointerEnterEvent details) { + _hoverX = details.localPosition.dx; + _hoverY = details.localPosition.dy; + if (onEnter) { + sendEvent("enter", { + "ts": details.timeStamp.inMilliseconds, + "kind": details.kind.name, + "gx": details.position.dx, + "gy": details.position.dy, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy + }); + } + } + + void handleHover(PointerHoverEvent details) { + var now = DateTime.now().millisecondsSinceEpoch; + if (now - _hoverTimestamp > hoverInterval) { + _hoverTimestamp = now; + var dx = details.localPosition.dx - _hoverX; + var dy = details.localPosition.dy - _hoverY; + _hoverX = details.localPosition.dx; + _hoverY = details.localPosition.dy; + sendEvent("hover", { + "ts": details.timeStamp.inMilliseconds, + "kind": details.kind.name, + "gx": details.position.dx, + "gy": details.position.dy, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "dx": dx, + "dy": dy, + }); + } + } + + var gd = (onTap | + onTapDown | + onTapUp | + onSecondaryTap | + onSecondaryTapDown | + onSecondaryTapUp | + onLongPressStart | + onLongPressEnd | + onSecondaryLongPressStart | + onSecondaryLongPressEnd | + onDoubleTap | + onDoubleTapDown | + onHorizontalDragStart | + onHorizontalDragUpdate | + onHorizontalDragEnd | + onVerticalDragStart | + onVerticalDragUpdate | + onVerticalDragEnd | + onPanStart | + onPanUpdate | + onPanEnd | + onScaleStart | + onScaleUpdate | + onScaleEnd) + ? GestureDetector( + onTap: onTap + ? () { + sendEvent("tap", ""); + } + : null, + onTapDown: onTapDown + ? (details) { + sendEvent("tap_down", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onTapUp: onTapUp + ? (details) { + sendEvent("tap_up", { + "kind": details.kind.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onSecondaryTap: onSecondaryTap + ? () { + sendEvent("secondary_tap", ""); + } + : null, + onSecondaryTapDown: onSecondaryTapDown + ? (details) { + sendEvent("secondary_tap_down", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onSecondaryTapUp: onSecondaryTapUp + ? (details) { + sendEvent("secondary_tap_up", { + "kind": details.kind.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onLongPressStart: onLongPressStart + ? (details) { + sendEvent("long_press_start", { + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onLongPressEnd: onLongPressEnd + ? (details) { + sendEvent("long_press_end", { + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + onSecondaryLongPressStart: onSecondaryLongPressStart + ? (details) { + sendEvent("secondary_long_press_start", { + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onSecondaryLongPressEnd: onSecondaryLongPressEnd + ? (details) { + sendEvent("secondary_long_press_end", { + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + onDoubleTap: onDoubleTap + ? () { + sendEvent("double_tap", ""); + } + : null, + onDoubleTapDown: onDoubleTapDown + ? (details) { + sendEvent("double_tap_down", { + "kind": details.kind?.name, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy, + "gx": details.globalPosition.dx, + "gy": details.globalPosition.dy, + }); + } + : null, + onHorizontalDragStart: + (onHorizontalDragStart || onHorizontalDragUpdate) + ? handleHorizontalDragStart + : null, + onHorizontalDragUpdate: onHorizontalDragUpdate + ? (details) { + handleHorizontalDragUpdate(details); + } + : null, + onHorizontalDragEnd: onHorizontalDragEnd + ? (details) { + sendEvent("horizontal_drag_end", { + "pv": details.primaryVelocity, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + onVerticalDragStart: (onVerticalDragStart || onVerticalDragUpdate) + ? handleVerticalDragStart + : null, + onVerticalDragUpdate: onVerticalDragUpdate + ? (details) { + handleVerticalDragUpdate(details); + } + : null, + onVerticalDragEnd: onVerticalDragEnd + ? (details) { + sendEvent("vertical_drag_end", { + "pv": details.primaryVelocity, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + onPanStart: (onPanStart || onPanUpdate) ? handlePanStart : null, + onPanUpdate: onPanUpdate + ? (details) { + handlePanUpdate(details); + } + : null, + onPanEnd: onPanEnd + ? (details) { + sendEvent("pan_end", { + "pv": details.primaryVelocity, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + onScaleStart: onScaleStart + ? (details) { + sendEvent("scale_start", { + "fpx": details.focalPoint.dx, + "fpy": details.focalPoint.dy, + "lfpx": details.localFocalPoint.dx, + "lfpy": details.localFocalPoint.dy, + "pc": details.pointerCount + }); + } + : null, + onScaleUpdate: onScaleUpdate + ? (details) { + sendEvent("scale_update", { + "fpx": details.focalPoint.dx, + "fpy": details.focalPoint.dy, + "fpdx": details.focalPointDelta.dx, + "fpdy": details.focalPointDelta.dy, + "lfpx": details.localFocalPoint.dx, + "lfpy": details.localFocalPoint.dy, + "pc": details.pointerCount, + "hs": details.horizontalScale, + "vs": details.verticalScale, + "s": details.scale, + "r": details.rotation, + }); + } + : null, + onScaleEnd: onScaleEnd + ? (details) { + sendEvent("scale_end", { + "pc": details.pointerCount, + "vx": details.velocity.pixelsPerSecond.dx, + "vy": details.velocity.pixelsPerSecond.dy + }); + } + : null, + child: content) + : null; + + var mouseCursor = widget.control.attrString("mouseCursor"); + result = ((mouseCursor != null) || onHover || onEnter || onExit) + ? MouseRegion( + cursor: parseMouseCursor(mouseCursor), + onHover: onHover + ? (details) { + handleHover(details); + } + : null, + onEnter: (onEnter || onHover) ? handleEnter : null, + onExit: onExit + ? (details) { + sendEvent("exit", { + "ts": details.timeStamp.inMilliseconds, + "kind": details.kind.name, + "gx": details.position.dx, + "gy": details.position.dy, + "lx": details.localPosition.dx, + "ly": details.localPosition.dy + }); + } + : null, + child: gd ?? content, + ) + : gd; + + if (result == null) { + return const ErrorControl( + "GestureDetector should have at least one event handler defined."); + } + + return constrainedControl(context, result, widget.parent, widget.control); + } + + MouseCursor parseMouseCursor(String? cursor) { + switch (cursor) { + case "alias": + return SystemMouseCursors.alias; + case "allScroll": + return SystemMouseCursors.allScroll; + case "basic": + return SystemMouseCursors.basic; + case "cell": + return SystemMouseCursors.cell; + case "click": + return SystemMouseCursors.click; + case "contextMenu": + return SystemMouseCursors.contextMenu; + case "copy": + return SystemMouseCursors.copy; + case "disappearing": + return SystemMouseCursors.disappearing; + case "forbidden": + return SystemMouseCursors.forbidden; + case "grab": + return SystemMouseCursors.grab; + case "grabbing": + return SystemMouseCursors.grabbing; + case "help": + return SystemMouseCursors.help; + case "move": + return SystemMouseCursors.move; + case "noDrop": + return SystemMouseCursors.noDrop; + case "none": + return SystemMouseCursors.none; + case "precise": + return SystemMouseCursors.precise; + case "progress": + return SystemMouseCursors.progress; + case "resizeColumn": + return SystemMouseCursors.resizeColumn; + case "resizeDown": + return SystemMouseCursors.resizeDown; + case "resizeDownLeft": + return SystemMouseCursors.resizeDownLeft; + case "resizeDownRight": + return SystemMouseCursors.resizeDownRight; + case "resizeLeft": + return SystemMouseCursors.resizeLeft; + case "resizeLeftRight": + return SystemMouseCursors.resizeLeftRight; + case "resizeRight": + return SystemMouseCursors.resizeRight; + case "resizeRow": + return SystemMouseCursors.resizeRow; + case "resizeUp": + return SystemMouseCursors.resizeUp; + case "resizeUpDown": + return SystemMouseCursors.resizeUpDown; + case "resizeUpLeft": + return SystemMouseCursors.resizeUpLeft; + case "resizeUpLeftDownRight": + return SystemMouseCursors.resizeUpLeftDownRight; + case "resizeUpRight": + return SystemMouseCursors.resizeUpRight; + case "resizeUpRightDownLeft": + return SystemMouseCursors.resizeUpRightDownLeft; + case "text": + return SystemMouseCursors.text; + case "verticalText": + return SystemMouseCursors.verticalText; + case "wait": + return SystemMouseCursors.wait; + case "zoomIn": + return SystemMouseCursors.zoomIn; + case "zoomOut": + return SystemMouseCursors.zoomOut; + default: + return MouseCursor.defer; + } + } +} diff --git a/package/lib/src/controls/grid_view.dart b/package/lib/src/controls/grid_view.dart index 1a323db63e..7a77cf2fe7 100644 --- a/package/lib/src/controls/grid_view.dart +++ b/package/lib/src/controls/grid_view.dart @@ -34,30 +34,40 @@ class GridViewControl extends StatelessWidget { List visibleControls = children.where((c) => c.isVisible).toList(); - var gridDelegate = maxExtent == null - ? SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: runsCount, - mainAxisSpacing: spacing, - crossAxisSpacing: runSpacing, - childAspectRatio: childAspectRatio) - : SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: maxExtent, - mainAxisSpacing: spacing, - crossAxisSpacing: runSpacing, - childAspectRatio: childAspectRatio); - - return constrainedControl( - context, - GridView.builder( + var gridView = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + debugPrint("constraints.maxWidth: ${constraints.maxWidth}"); + debugPrint("constraints.maxHeight: ${constraints.maxHeight}"); + + var shrinkWrap = + (!horizontal && constraints.maxHeight == double.infinity) || + (horizontal && constraints.maxWidth == double.infinity); + + var gridDelegate = maxExtent == null + ? SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: runsCount, + mainAxisSpacing: spacing, + crossAxisSpacing: runSpacing, + childAspectRatio: childAspectRatio) + : SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: maxExtent, + mainAxisSpacing: spacing, + crossAxisSpacing: runSpacing, + childAspectRatio: childAspectRatio); + + return GridView.builder( scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, + shrinkWrap: shrinkWrap, padding: padding, gridDelegate: gridDelegate, itemCount: visibleControls.length, itemBuilder: (context, index) { return createControl(control, visibleControls[index].id, disabled); }, - ), - parent, - control); + ); + }, + ); + + return constrainedControl(context, gridView, parent, control); } } diff --git a/package/lib/src/controls/image.dart b/package/lib/src/controls/image.dart index f225a4cc40..43d40d95e0 100644 --- a/package/lib/src/controls/image.dart +++ b/package/lib/src/controls/image.dart @@ -1,4 +1,5 @@ import 'dart:convert'; + import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -19,8 +20,7 @@ class ImageControl extends StatelessWidget { final Control? parent; final Control control; - // ignore: constant_identifier_names - static const String SVG_TAG = " 0 + var listView = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + debugPrint("constraints.maxWidth: ${constraints.maxWidth}"); + debugPrint("constraints.maxHeight: ${constraints.maxHeight}"); + + var shrinkWrap = + (!horizontal && constraints.maxHeight == double.infinity) || + (horizontal && constraints.maxWidth == double.infinity); + + return spacing > 0 ? ListView.separated( controller: _controller, scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, + shrinkWrap: shrinkWrap, padding: padding, itemCount: children.length, itemBuilder: (context, index) { @@ -77,6 +85,7 @@ class ListViewControl extends StatelessWidget { : ListView.builder( controller: _controller, scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, + shrinkWrap: shrinkWrap, padding: padding, itemCount: children.length, itemExtent: itemExtent, @@ -87,8 +96,10 @@ class ListViewControl extends StatelessWidget { prototypeItem: firstItemPrototype && children.isNotEmpty ? createControl(control, visibleControls[0].id, disabled) : null, - ), - parent, - control); + ); + }, + ); + + return constrainedControl(context, listView, parent, control); } } diff --git a/package/lib/src/controls/navigation_rail.dart b/package/lib/src/controls/navigation_rail.dart index 3c9d5159e7..fffd38bbe6 100644 --- a/package/lib/src/controls/navigation_rail.dart +++ b/package/lib/src/controls/navigation_rail.dart @@ -1,3 +1,4 @@ +import 'package:flet/src/controls/error.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; @@ -85,56 +86,72 @@ class _NavigationRailControlState extends State { builder: (content, viewModel) { _dispatch = viewModel.dispatch; - return NavigationRail( - labelType: extended ? NavigationRailLabelType.none : labelType, - extended: extended, - minWidth: widget.control.attrDouble("minWidth"), - minExtendedWidth: widget.control.attrDouble("minExtendedWidth"), - groupAlignment: widget.control.attrDouble("groupAlignment"), - backgroundColor: HexColor.fromString( - Theme.of(context), widget.control.attrString("bgColor", "")!), - leading: leadingCtrls.isNotEmpty - ? createControl( - widget.control, leadingCtrls.first.id, disabled) - : null, - trailing: trailingCtrls.isNotEmpty - ? createControl( - widget.control, trailingCtrls.first.id, disabled) - : null, - selectedIndex: _selectedIndex, - onDestinationSelected: _destinationChanged, - destinations: viewModel.controlViews.map((destView) { - var label = destView.control.attrString("label", "")!; - var labelContentCtrls = - destView.children.where((c) => c.name == "label_content"); - - var icon = - getMaterialIcon(destView.control.attrString("icon", "")!); - var iconContentCtrls = - destView.children.where((c) => c.name == "icon_content"); - - var selectedIcon = getMaterialIcon( - destView.control.attrString("selectedIcon", "")!); - var selectedIconContentCtrls = destView.children - .where((c) => c.name == "selected_icon_content"); - - return NavigationRailDestination( - padding: parseEdgeInsets(destView.control, "padding"), - icon: iconContentCtrls.isNotEmpty - ? createControl(destView.control, - iconContentCtrls.first.id, disabled) - : Icon(icon), - selectedIcon: selectedIconContentCtrls.isNotEmpty - ? createControl(destView.control, - selectedIconContentCtrls.first.id, disabled) - : selectedIcon != null - ? Icon(selectedIcon) - : null, - label: labelContentCtrls.isNotEmpty - ? createControl(destView.control, - labelContentCtrls.first.id, disabled) - : Text(label)); - }).toList()); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + debugPrint("constraints.maxWidth: ${constraints.maxWidth}"); + debugPrint("constraints.maxHeight: ${constraints.maxHeight}"); + + if (constraints.maxHeight == double.infinity && + widget.control.attrs["height"] == null) { + return const ErrorControl("Error displaying NavigationRail", + description: + "Control's height is unbounded. Either set \"expand\" property, set a fixed \"height\" or nest NavigationRail inside another control with a fixed height."); + } + + return NavigationRail( + labelType: + extended ? NavigationRailLabelType.none : labelType, + extended: extended, + minWidth: widget.control.attrDouble("minWidth"), + minExtendedWidth: + widget.control.attrDouble("minExtendedWidth"), + groupAlignment: widget.control.attrDouble("groupAlignment"), + backgroundColor: HexColor.fromString(Theme.of(context), + widget.control.attrString("bgColor", "")!), + leading: leadingCtrls.isNotEmpty + ? createControl( + widget.control, leadingCtrls.first.id, disabled) + : null, + trailing: trailingCtrls.isNotEmpty + ? createControl( + widget.control, trailingCtrls.first.id, disabled) + : null, + selectedIndex: _selectedIndex, + onDestinationSelected: _destinationChanged, + destinations: viewModel.controlViews.map((destView) { + var label = destView.control.attrString("label", "")!; + var labelContentCtrls = destView.children + .where((c) => c.name == "label_content"); + + var icon = getMaterialIcon( + destView.control.attrString("icon", "")!); + var iconContentCtrls = destView.children + .where((c) => c.name == "icon_content"); + + var selectedIcon = getMaterialIcon( + destView.control.attrString("selectedIcon", "")!); + var selectedIconContentCtrls = destView.children + .where((c) => c.name == "selected_icon_content"); + + return NavigationRailDestination( + padding: parseEdgeInsets(destView.control, "padding"), + icon: iconContentCtrls.isNotEmpty + ? createControl(destView.control, + iconContentCtrls.first.id, disabled) + : Icon(icon), + selectedIcon: selectedIconContentCtrls.isNotEmpty + ? createControl(destView.control, + selectedIconContentCtrls.first.id, disabled) + : selectedIcon != null + ? Icon(selectedIcon) + : null, + label: labelContentCtrls.isNotEmpty + ? createControl(destView.control, + labelContentCtrls.first.id, disabled) + : Text(label)); + }).toList()); + }, + ); }); return constrainedControl(context, rail, widget.parent, widget.control); diff --git a/package/lib/src/controls/page.dart b/package/lib/src/controls/page.dart index 860d4c8c7d..4f69e4f812 100644 --- a/package/lib/src/controls/page.dart +++ b/package/lib/src/controls/page.dart @@ -9,7 +9,6 @@ import '../actions.dart'; import '../flet_app_services.dart'; import '../models/app_state.dart'; import '../models/control.dart'; -import '../models/control_type.dart'; import '../models/control_view_model.dart'; import '../models/controls_view_model.dart'; import '../models/page_media_view_model.dart'; @@ -558,10 +557,10 @@ class _PageControlState extends State { bool firstControl = true; for (var ctrl in children.where((c) => c.isVisible)) { - if (ctrl.type == ControlType.appBar) { + if (ctrl.type == "appbar") { appBar = ctrl; continue; - } else if (ctrl.type == ControlType.floatingActionButton) { + } else if (ctrl.type == "floatingactionbutton") { fab = ctrl; continue; } diff --git a/package/lib/src/controls/radio.dart b/package/lib/src/controls/radio.dart index 557219061e..021e264bc3 100644 --- a/package/lib/src/controls/radio.dart +++ b/package/lib/src/controls/radio.dart @@ -6,8 +6,8 @@ import '../flet_app_services.dart'; import '../models/app_state.dart'; import '../models/control.dart'; import '../models/control_ancestor_view_model.dart'; -import '../models/control_type.dart'; import '../protocol/update_control_props_payload.dart'; +import '../utils/buttons.dart'; import 'create_control.dart'; import 'error.dart'; @@ -70,7 +70,7 @@ class _RadioControlState extends State { return StoreConnector( distinct: true, converter: (store) => ControlAncestorViewModel.fromStore( - store, widget.control.id, ControlType.radioGroup), + store, widget.control.id, "radiogroup"), builder: (context, viewModel) { debugPrint("Radio StoreConnector build: ${widget.control.id}"); @@ -103,6 +103,8 @@ class _RadioControlState extends State { focusNode: _focusNode, groupValue: groupValue, value: value, + fillColor: parseMaterialStateColor( + Theme.of(context), widget.control, "fillColor"), onChanged: !disabled ? (String? value) { onChange(value); diff --git a/package/lib/src/controls/scrollable_control.dart b/package/lib/src/controls/scrollable_control.dart index eecace32dc..c4545d4844 100644 --- a/package/lib/src/controls/scrollable_control.dart +++ b/package/lib/src/controls/scrollable_control.dart @@ -67,8 +67,8 @@ class _ScrollableControlState extends State { controller: _controller, child: SingleChildScrollView( controller: _controller, - child: widget.child, scrollDirection: widget.scrollDirection, + child: widget.child, )); } } diff --git a/package/lib/src/controls/snack_bar.dart b/package/lib/src/controls/snack_bar.dart index 6fa56af755..ea0f8b0a11 100644 --- a/package/lib/src/controls/snack_bar.dart +++ b/package/lib/src/controls/snack_bar.dart @@ -43,6 +43,8 @@ class _SnackBarControlState extends State { SnackBarAction? action = actionName != "" ? SnackBarAction( label: actionName, + textColor: HexColor.fromString(Theme.of(context), + widget.control.attrString("actionColor", "")!), onPressed: () { debugPrint("SnackBar ${widget.control.id} clicked!"); FletAppServices.of(context).ws.pageEventFromWeb( diff --git a/package/lib/src/controls/textfield.dart b/package/lib/src/controls/textfield.dart index 7d4501ac93..f75d388e38 100644 --- a/package/lib/src/controls/textfield.dart +++ b/package/lib/src/controls/textfield.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flet/src/utils/borders.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_redux/flutter_redux.dart'; @@ -221,6 +222,9 @@ class _TextFieldControlState extends State { suffixControls.isNotEmpty ? suffixControls.first : null, revealPasswordIcon, _focused), + cursorHeight: widget.control.attrDouble("cursorHeight"), + cursorWidth: widget.control.attrDouble("cursorWidth") ?? 2.0, + cursorRadius: parseRadius(widget.control, "cursorRadius"), keyboardType: keyboardType, textAlign: textAlign, minLines: minLines, @@ -302,7 +306,6 @@ class TextCapitalizationFormatter extends TextInputFormatter { List sentences = newValue.text.split('.'); for (int i = 0; i < sentences.length; i++) { sentences[i] = inCaps(sentences[i]); - debugPrint(sentences[i]); } text = sentences.join('.'); break; diff --git a/package/lib/src/flet_app.dart b/package/lib/src/flet_app.dart index 138466b22d..1a1b82e5d0 100644 --- a/package/lib/src/flet_app.dart +++ b/package/lib/src/flet_app.dart @@ -1,16 +1,21 @@ -import 'flet_app_services.dart'; import 'package:flutter/material.dart'; +import 'flet_app_errors_handler.dart'; import 'flet_app_main.dart'; +import 'flet_app_services.dart'; class FletApp extends StatelessWidget { final String pageUrl; final String? title; + final FletAppErrorsHandler? errorsHandler; - const FletApp({Key? key, required this.pageUrl, this.title}) + const FletApp( + {Key? key, required this.pageUrl, this.title, this.errorsHandler}) : super(key: key); @override Widget build(BuildContext context) => FletAppServices( - pageUrl: pageUrl, child: FletAppMain(title: title ?? "Flet")); + pageUrl: pageUrl, + errorsHandler: errorsHandler, + child: FletAppMain(title: title ?? "Flet")); } diff --git a/package/lib/src/flet_app_errors_handler.dart b/package/lib/src/flet_app_errors_handler.dart new file mode 100644 index 0000000000..065a902518 --- /dev/null +++ b/package/lib/src/flet_app_errors_handler.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +class FletAppErrorsHandler extends ChangeNotifier { + String? _error; + String? get error => _error; + void onError(String error) { + _error = error; + notifyListeners(); + } +} diff --git a/package/lib/src/flet_app_services.dart b/package/lib/src/flet_app_services.dart index 27608e98a2..f0d03be49a 100644 --- a/package/lib/src/flet_app_services.dart +++ b/package/lib/src/flet_app_services.dart @@ -1,3 +1,4 @@ +import 'package:flet/src/flet_app_errors_handler.dart'; import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; @@ -8,13 +9,28 @@ import 'web_socket_client.dart'; class FletAppServices extends InheritedWidget { final String pageUrl; + final FletAppErrorsHandler? errorsHandler; late final WebSocketClient ws; late final Store store; - FletAppServices({Key? key, required Widget child, required this.pageUrl}) + FletAppServices( + {Key? key, + required Widget child, + required this.pageUrl, + this.errorsHandler}) : super(key: key, child: child) { store = Store(appReducer, initialState: AppState.initial()); ws = WebSocketClient(store); + if (errorsHandler != null) { + errorsHandler!.addListener(() { + if (store.state.isRegistered) { + ws.pageEventFromWeb( + eventTarget: "page", + eventName: "error", + eventData: errorsHandler!.error!); + } + }); + } // connect to a page var pageUri = Uri.parse(pageUrl); store.dispatch(PageLoadAction(pageUri, ws)); diff --git a/package/lib/src/models/app_state.dart b/package/lib/src/models/app_state.dart index f724fa7e47..8fae5410b3 100644 --- a/package/lib/src/models/app_state.dart +++ b/package/lib/src/models/app_state.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:equatable/equatable.dart'; -import 'control_type.dart'; import 'control.dart'; @@ -61,7 +60,7 @@ class AppState extends Equatable { "page": Control( id: "page", pid: "", - type: ControlType.page, + type: "page", name: "", childIds: [], attrs: {}) diff --git a/package/lib/src/models/control.dart b/package/lib/src/models/control.dart index 950d98cb91..6dbcefcc5b 100644 --- a/package/lib/src/models/control.dart +++ b/package/lib/src/models/control.dart @@ -1,13 +1,11 @@ import 'package:equatable/equatable.dart'; -import 'control_type.dart'; - class Control extends Equatable { static const reservedProps = ['i', 'p', 't', 'c', 'n']; final String id; final String pid; - final ControlType type; + final String type; final String? name; final List childIds; final Map attrs; @@ -31,8 +29,7 @@ class Control extends Equatable { return Control( id: json['i'] as String, pid: json['p'] as String, - type: ControlType.values.firstWhere( - (t) => t.name.toLowerCase() == (json['t'] as String).toLowerCase()), + type: (json['t'] as String).toLowerCase(), name: json['n'] as String?, childIds: List.from(json['c']), attrs: attrs); @@ -78,7 +75,7 @@ class Control extends Equatable { Control copyWith( {String? id, String? pid, - ControlType? type, + String? type, String? name, List? childIds, Map? attrs}) => diff --git a/package/lib/src/models/control_ancestor_view_model.dart b/package/lib/src/models/control_ancestor_view_model.dart index 7960db5474..0da785cf15 100644 --- a/package/lib/src/models/control_ancestor_view_model.dart +++ b/package/lib/src/models/control_ancestor_view_model.dart @@ -1,5 +1,4 @@ import 'package:equatable/equatable.dart'; -import 'control_type.dart'; import 'package:redux/redux.dart'; import 'app_state.dart'; @@ -13,7 +12,7 @@ class ControlAncestorViewModel extends Equatable { {required this.ancestor, required this.dispatch}); static ControlAncestorViewModel fromStore( - Store store, String id, ControlType ancestorType) { + Store store, String id, String ancestorType) { Control? ancestor; String controlId = id; while (true) { @@ -22,7 +21,7 @@ class ControlAncestorViewModel extends Equatable { break; } ancestor = store.state.controls[parentId]!; - if (ancestor.type == ancestorType) { + if (ancestor.type.toLowerCase() == ancestorType.toLowerCase()) { break; } controlId = ancestor.id; diff --git a/package/lib/src/models/control_type.dart b/package/lib/src/models/control_type.dart deleted file mode 100644 index 70fc93aea9..0000000000 --- a/package/lib/src/models/control_type.dart +++ /dev/null @@ -1,54 +0,0 @@ -enum ControlType { - alertDialog, - animatedSwitcher, - appBar, - audio, - banner, - card, - checkbox, - circleAvatar, - column, - container, - divider, - draggable, - dragTarget, - dropdown, - dropdownOption, - elevatedButton, - filePicker, - floatingActionButton, - gridView, - icon, - iconButton, - image, - listTile, - listView, - markdown, - navigationRail, - navigationRailDestination, - outlinedButton, - offstage, - page, - popupMenuButton, - popupMenuItem, - progressBar, - progressRing, - radioGroup, - radio, - row, - semantics, - shaderMask, - slider, - snackBar, - stack, - // ignore: constant_identifier_names - Switch, - tabs, - tab, - text, - textButton, - textField, - verticalDivider, - view, - windowDragArea -} diff --git a/package/lib/src/models/routes_view_model.dart b/package/lib/src/models/routes_view_model.dart index 9c05eb6f0c..fffb8e8006 100644 --- a/package/lib/src/models/routes_view_model.dart +++ b/package/lib/src/models/routes_view_model.dart @@ -4,7 +4,6 @@ import 'package:redux/redux.dart'; import 'app_state.dart'; import 'control.dart'; -import 'control_type.dart'; class RoutesViewModel extends Equatable { final Control page; @@ -23,7 +22,7 @@ class RoutesViewModel extends Equatable { static RoutesViewModel fromStore(Store store) { Control? offstageControl = store.state.controls["page"]!.childIds .map((childId) => store.state.controls[childId]!) - .firstWhereOrNull((c) => c.type == ControlType.offstage); + .firstWhereOrNull((c) => c.type == "offstage"); return RoutesViewModel( page: store.state.controls["page"]!, @@ -37,7 +36,7 @@ class RoutesViewModel extends Equatable { : [], viewIds: store.state.controls["page"]!.childIds .map((childId) => store.state.controls[childId]!) - .where((c) => c.type != ControlType.offstage && c.isVisible) + .where((c) => c.type != "offstage" && c.isVisible) .map((c) => c.id) .toList()); } diff --git a/package/lib/src/protocol/drag_target_accept_event.dart b/package/lib/src/protocol/drag_target_accept_event.dart new file mode 100644 index 0000000000..0153520d9d --- /dev/null +++ b/package/lib/src/protocol/drag_target_accept_event.dart @@ -0,0 +1,17 @@ +class DragTargetAcceptEvent { + final String srcId; + final double x; + final double y; + + DragTargetAcceptEvent({ + required this.srcId, + required this.x, + required this.y, + }); + + Map toJson() => { + 'src_id': srcId, + 'x': x, + 'y': y, + }; +} diff --git a/package/lib/src/reducers.dart b/package/lib/src/reducers.dart index 477e6b4077..180fdb4cae 100644 --- a/package/lib/src/reducers.dart +++ b/package/lib/src/reducers.dart @@ -1,8 +1,4 @@ -// One simple action: Increment -import 'dart:convert'; - import 'package:flet/src/utils/client_storage.dart'; -import 'package:flet/src/utils/clipboard.dart'; import 'package:flet/src/utils/launch_url.dart'; import 'package:flutter/foundation.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -13,7 +9,6 @@ import 'models/control.dart'; import 'models/window_media_data.dart'; import 'protocol/add_page_controls_payload.dart'; import 'protocol/clean_control_payload.dart'; -import 'protocol/invoke_method_result.dart'; import 'protocol/message.dart'; import 'protocol/remove_control_payload.dart'; import 'protocol/update_control_props_payload.dart'; @@ -213,16 +208,6 @@ AppState appReducer(AppState state, dynamic action) { int.tryParse(action.payload.args["window_width"] ?? ""), int.tryParse(action.payload.args["window_height"] ?? "")); break; - case "setClipboard": - setClipboard(action.payload.args["value"]!); - break; - case "getClipboard": - getClipboard().then((value) => action.ws.pageEventFromWeb( - eventTarget: "page", - eventName: "invoke_method_result", - eventData: json.encode(InvokeMethodResult( - methodId: action.payload.methodId, result: value)))); - break; case "windowToFront": windowToFront(); break; diff --git a/package/lib/src/utils/borders.dart b/package/lib/src/utils/borders.dart index cfab1a8c62..427640c0c8 100644 --- a/package/lib/src/utils/borders.dart +++ b/package/lib/src/utils/borders.dart @@ -16,6 +16,15 @@ BorderRadius? parseBorderRadius(Control control, String propName) { return borderRadiusFromJSON(j1); } +Radius? parseRadius(Control control, String propName) { + var r = control.attrDouble(propName, null); + if (r == null) { + return null; + } + + return Radius.circular(r); +} + Border? parseBorder(ThemeData theme, Control control, String propName, {Color? defaultSideColor}) { var v = control.attrString(propName, null); diff --git a/package/lib/src/utils/buttons.dart b/package/lib/src/utils/buttons.dart index 89a9e05467..7592065e6f 100644 --- a/package/lib/src/utils/buttons.dart +++ b/package/lib/src/utils/buttons.dart @@ -1,12 +1,12 @@ import 'dart:convert'; -import 'borders.dart'; -import 'edge_insets.dart'; -import 'numbers.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; +import 'borders.dart'; import 'colors.dart'; +import 'edge_insets.dart'; +import 'numbers.dart'; ButtonStyle? parseButtonStyle(ThemeData theme, Control control, String propName, {required Color defaultForegroundColor, @@ -38,6 +38,18 @@ ButtonStyle? parseButtonStyle(ThemeData theme, Control control, String propName, defaultShape); } +MaterialStateProperty? parseMaterialStateColor( + ThemeData theme, Control control, String propName) { + var v = control.attrString(propName, null); + if (v == null) { + return null; + } + + final j1 = json.decode(v); + return getMaterialStateProperty( + j1, (jv) => HexColor.fromString(theme, jv as String), null); +} + ButtonStyle? buttonStyleFromJSON( ThemeData theme, Map json, diff --git a/package/lib/src/utils/clipboard.dart b/package/lib/src/utils/clipboard.dart deleted file mode 100644 index 46853e8fcf..0000000000 --- a/package/lib/src/utils/clipboard.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/services.dart'; - -Future setClipboard(String value) async { - await Clipboard.setData(ClipboardData(text: value)); -} - -Future getClipboard() async { - return (await Clipboard.getData(Clipboard.kTextPlain))?.text; -} diff --git a/package/lib/src/utils/platform_utils_web.dart b/package/lib/src/utils/platform_utils_web.dart index b79543ef92..43e58340b9 100644 --- a/package/lib/src/utils/platform_utils_web.dart +++ b/package/lib/src/utils/platform_utils_web.dart @@ -1,3 +1,4 @@ +// ignore: avoid_web_libraries_in_flutter import 'dart:html' as html; bool isProgressiveWebApp() { diff --git a/package/lib/src/utils/session_store_web.dart b/package/lib/src/utils/session_store_web.dart index 866f498d6e..65428b3566 100644 --- a/package/lib/src/utils/session_store_web.dart +++ b/package/lib/src/utils/session_store_web.dart @@ -1,6 +1,5 @@ -// import 'dart:html'; - -import 'dart:html'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html' as html; import 'package:flutter/foundation.dart'; @@ -8,11 +7,11 @@ class SessionStore { static String? get(String name) { debugPrint("Get session storage $name"); - return window.sessionStorage[name]; + return html.window.sessionStorage[name]; } static void set(String name, String value) { debugPrint("Set session storage $name"); - window.sessionStorage[name] = value; + html.window.sessionStorage[name] = value; } } diff --git a/package/lib/src/utils/text.dart b/package/lib/src/utils/text.dart index d7cb248ee7..531b385f98 100644 --- a/package/lib/src/utils/text.dart +++ b/package/lib/src/utils/text.dart @@ -1,5 +1,11 @@ +import 'dart:convert'; + +import 'package:flet/src/utils/numbers.dart'; import 'package:flutter/material.dart'; +import '../models/control.dart'; +import 'colors.dart'; + TextStyle? getTextStyle(BuildContext context, String styleName) { var textTheme = Theme.of(context).textTheme; switch (styleName.toLowerCase()) { @@ -64,3 +70,24 @@ FontWeight? getFontWeight(String weightName) { } return null; } + +TextStyle? parseTextStyle(ThemeData theme, Control control, String propName) { + dynamic j; + var v = control.attrString(propName, null); + if (v == null) { + return null; + } + j = json.decode(v); + return textStyleFromJson(theme, j); +} + +TextStyle textStyleFromJson(ThemeData theme, Map json) { + return TextStyle( + fontSize: parseDouble(json["size"]), + fontWeight: getFontWeight(json["weight"] ?? ""), + fontStyle: + (json["italic"] ?? false) ? FontStyle.italic : FontStyle.normal, + fontFamily: json["font_family"], + color: HexColor.fromString(theme, json["color"] ?? ""), + backgroundColor: HexColor.fromString(theme, json["bgcolor"] ?? "")); +} diff --git a/package/pubspec.yaml b/package/pubspec.yaml index 67d14b9bb3..7103c972b0 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -2,7 +2,7 @@ name: flet description: Write entire Flutter app in Python or add server-driven UI experience into existing Flutter app. homepage: https://flet.dev repository: https://github.com/flet-dev/flet -version: 0.0.1 +version: 0.1.62 environment: sdk: ">=2.18.1 <3.0.0" @@ -20,12 +20,12 @@ dependencies: collection: ^1.16.0 url_launcher: ^6.1.5 flutter_markdown: ^0.6.10+3 + markdown: ^6.0.1 file_picker: ^5.0.1 shared_preferences: ^2.0.15 flutter_svg: ^1.1.5 window_to_front: ^0.0.3 audioplayers: ^1.1.0 - universal_html: ^1.2.1 dev_dependencies: flutter_test: diff --git a/package/test/controls/control_test.dart b/package/test/controls/control_test.dart index 6567adc4f8..da08021f58 100644 --- a/package/test/controls/control_test.dart +++ b/package/test/controls/control_test.dart @@ -1,15 +1,14 @@ import 'dart:convert'; -import 'package:flet/src/models/control_type.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:flet/src/models/control.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { test("Two controls are equal", () { Control c1 = const Control( id: "i1", pid: "p1", - type: ControlType.stack, + type: "stack", name: null, childIds: ["txt1", "btn1"], attrs: {"text": "Hello!", "width": "200"}); @@ -17,7 +16,7 @@ void main() { Control c2 = const Control( id: "i1", pid: "p1", - type: ControlType.stack, + type: "stack", name: null, childIds: ["txt1", "btn1"], attrs: {"width": "200", "text": "Hello!"}); @@ -41,7 +40,7 @@ void main() { expect(c1.id, "s1"); expect(c1.pid, ""); - expect(c1.type, ControlType.stack); + expect(c1.type, "stack"); expect(c1.name, "content"); expect(c1.childIds, ["txt1", "txt2"]); expect(c1.attrs.length, 2); diff --git a/sdk/python/flet/__init__.py b/sdk/python/flet/__init__.py index df1cbdf4fe..f91446a3fa 100644 --- a/sdk/python/flet/__init__.py +++ b/sdk/python/flet/__init__.py @@ -12,7 +12,7 @@ from flet.container import Container, ContainerTapEvent from flet.control import Control from flet.divider import Divider -from flet.drag_target import DragTarget +from flet.drag_target import DragTarget, DragTargetAcceptEvent from flet.draggable import Draggable from flet.dropdown import Dropdown from flet.elevated_button import ElevatedButton @@ -26,6 +26,20 @@ from flet.filled_tonal_button import FilledTonalButton from flet.flet import * from flet.floating_action_button import FloatingActionButton +from flet.gesture_detector import ( + DragEndEvent, + DragStartEvent, + DragUpdateEvent, + GestureDetector, + HoverEvent, + LongPressEndEvent, + LongPressStartEvent, + MouseCursor, + ScaleEndEvent, + ScaleStartEvent, + ScaleUpdateEvent, + TapEvent, +) from flet.gradients import LinearGradient, RadialGradient, SweepGradient from flet.grid_view import GridView from flet.icon import Icon @@ -56,6 +70,7 @@ from flet.template_route import TemplateRoute from flet.text import Text from flet.text_button import TextButton +from flet.text_style import TextStyle from flet.textfield import TextField from flet.theme import PageTransitionsTheme, Theme from flet.user_control import UserControl diff --git a/sdk/python/flet/app_bar.py b/sdk/python/flet/app_bar.py index 3cd21da1a3..ad8021b93e 100644 --- a/sdk/python/flet/app_bar.py +++ b/sdk/python/flet/app_bar.py @@ -13,6 +13,7 @@ def __init__( ref: Optional[Ref] = None, leading: Optional[Control] = None, leading_width: OptionalNumber = None, + automatically_imply_leading: Optional[bool] = None, title: Optional[Control] = None, center_title: Optional[bool] = None, toolbar_height: OptionalNumber = None, @@ -29,6 +30,7 @@ def __init__( self.leading = leading self.leading_width = leading_width + self.automatically_imply_leading = automatically_imply_leading self.title = title self.center_title = center_title self.toolbar_height = toolbar_height @@ -73,6 +75,18 @@ def leading_width(self) -> OptionalNumber: def leading_width(self, value: OptionalNumber): self._set_attr("leadingWidth", value) + # automatically_imply_leading + @property + def automatically_imply_leading(self) -> Optional[bool]: + return self._get_attr( + "automaticallyImplyLeading", data_type="bool", def_value=True + ) + + @automatically_imply_leading.setter + @beartype + def automatically_imply_leading(self, value: Optional[bool]): + self._set_attr("automaticallyImplyLeading", value) + # title @property def title(self) -> Optional[Control]: diff --git a/sdk/python/flet/audio.py b/sdk/python/flet/audio.py index 92026ba58e..c2adddb6e0 100644 --- a/sdk/python/flet/audio.py +++ b/sdk/python/flet/audio.py @@ -41,6 +41,7 @@ def __init__( autoplay: Optional[bool] = None, volume: OptionalNumber = None, balance: OptionalNumber = None, + playback_rate: OptionalNumber = None, release_mode: Optional[ReleaseMode] = None, on_loaded=None, on_duration_changed=None, @@ -64,6 +65,7 @@ def __init__( self.autoplay = autoplay self.volume = volume self.balance = balance + self.playback_rate = playback_rate self.release_mode = release_mode self.on_loaded = on_loaded self.on_duration_changed = on_duration_changed @@ -91,11 +93,13 @@ def seek(self, position_milliseconds: int): "seek", params=[str(position_milliseconds)], wait_for_result=False ) - def get_duration(self) -> int: - return self._call_method("get_duration", []) + def get_duration(self) -> Optional[int]: + sr = self._call_method("get_duration", []) + return int(sr) if sr else None - def get_current_position(self) -> int: - return self._call_method("get_current_position", []) + def get_current_position(self) -> Optional[int]: + sr = self._call_method("get_current_position", []) + return int(sr) if sr else None def _call_method(self, name: str, params: List[str], wait_for_result=True) -> Any: m = AudioMethodCall(i=self.__call_counter, n=name, p=params) @@ -181,6 +185,17 @@ def balance(self, value: OptionalNumber): if value is None or (value >= -1 and value <= 1): self._set_attr("balance", value) + # playback_rate + @property + def playback_rate(self) -> OptionalNumber: + return self._get_attr("playbackRate") + + @playback_rate.setter + @beartype + def playback_rate(self, value: OptionalNumber): + if value is None or (value >= 0 and value <= 2): + self._set_attr("playbackRate", value) + # release_mode @property def release_mode(self): diff --git a/sdk/python/flet/callable_control.py b/sdk/python/flet/callable_control.py new file mode 100644 index 0000000000..8daccc3ddc --- /dev/null +++ b/sdk/python/flet/callable_control.py @@ -0,0 +1,78 @@ +import dataclasses +import json +import threading +from typing import Any, Dict, List, Optional + +from beartype import beartype + +from flet.control import Control +from flet.ref import Ref + + +@dataclasses.dataclass +class ControlMethodCall: + i: int + n: str + p: List[str] + + +@dataclasses.dataclass +class ControlMethodResults: + i: int + r: Optional[str] + e: Optional[str] + + +class CallableControl(Control): + def __init__( + self, + ref: Optional[Ref] = None, + data: Any = None, + ): + + Control.__init__( + self, + ref=ref, + data=data, + ) + + self.__call_counter = 0 + self.__calls: Dict[int, threading.Event] = {} + self.__results: Dict[threading.Event, tuple[Optional[str], Optional[str]]] = {} + self._add_event_handler("method_result", self._on_result) + + def _call_method(self, name: str, params: List[str], wait_for_result=True) -> Any: + m = ControlMethodCall(i=self.__call_counter, n=name, p=params) + self.__call_counter += 1 + self._set_attr_json("method", m) + + evt: Optional[threading.Event] = None + if wait_for_result: + evt = threading.Event() + self.__calls[m.i] = evt + self.update() + + if not wait_for_result: + return + + assert evt is not None + if not evt.wait(5): + del self.__calls[m.i] + raise Exception( + f"Timeout waiting for {self.__class__.__name__}.{name}({params}) method call" + ) + result, err = self.__results.pop(evt) + if err != None: + raise Exception(err) + if result == None: + return None + return json.loads(result) + + def _on_result(self, e): + d = json.loads(e.data) + result = ControlMethodResults(**d) + evt = self.__calls.pop(result.i, None) + if evt == None: + return + self.__results[evt] = (result.r, result.e) + evt.set() diff --git a/sdk/python/flet/checkbox.py b/sdk/python/flet/checkbox.py index 53684068fa..4d48b9186f 100644 --- a/sdk/python/flet/checkbox.py +++ b/sdk/python/flet/checkbox.py @@ -1,7 +1,9 @@ from typing import Any, Optional, Union from beartype import beartype +from beartype.typing import Dict +from flet.buttons import MaterialState from flet.constrained_control import ConstrainedControl from flet.control import OptionalNumber from flet.ref import Ref @@ -50,6 +52,8 @@ def __init__( value: Optional[bool] = None, tristate: Optional[bool] = None, autofocus: Optional[bool] = None, + fill_color: Union[None, str, Dict[MaterialState, str]] = None, + check_color: Optional[str] = None, on_change=None, on_focus=None, on_blur=None, @@ -85,6 +89,8 @@ def __init__( self.label = label self.label_position = label_position self.autofocus = autofocus + self.check_color = check_color + self.fill_color = fill_color self.on_change = on_change self.on_focus = on_focus self.on_blur = on_blur @@ -92,6 +98,13 @@ def __init__( def _get_control_name(self): return "checkbox" + def _before_build_command(self): + super()._before_build_command() + fc = self.__fill_color + if fc is not None and not isinstance(fc, Dict): + fc = {"": fc} + self._set_attr_json("fillColor", fc) + # value @property def value(self) -> Optional[bool]: @@ -143,6 +156,25 @@ def autofocus(self) -> Optional[bool]: def autofocus(self, value: Optional[bool]): self._set_attr("autofocus", value) + # check_color + @property + def check_color(self): + return self._get_attr("checkColor") + + @check_color.setter + def check_color(self, value): + self._set_attr("checkColor", value) + + # fill_color + @property + def fill_color(self) -> Union[None, str, Dict[MaterialState, str]]: + return self.__fill_color + + @fill_color.setter + @beartype + def fill_color(self, value: Union[None, str, Dict[MaterialState, str]]): + self.__fill_color = value + # on_change @property def on_change(self): diff --git a/sdk/python/flet/clipboard.py b/sdk/python/flet/clipboard.py new file mode 100644 index 0000000000..ffd0d87bf8 --- /dev/null +++ b/sdk/python/flet/clipboard.py @@ -0,0 +1,40 @@ +import dataclasses +import time +from typing import Any, Optional + +from beartype import beartype + +from flet.callable_control import CallableControl +from flet.ref import Ref + + +@dataclasses.dataclass +class ClipboardData: + ts: str + d: Optional[str] + + +class Clipboard(CallableControl): + def __init__( + self, + ref: Optional[Ref] = None, + data: Any = None, + ): + + CallableControl.__init__( + self, + ref=ref, + data=data, + ) + + def _get_control_name(self): + return "clipboard" + + def _is_isolated(self): + return True + + def set_data(self, data: str): + self._call_method("set_data", [data], wait_for_result=False) + + def get_data(self) -> str: + return self._call_method("get_data", []) diff --git a/sdk/python/flet/container.py b/sdk/python/flet/container.py index c651e46a50..9c304e8e9c 100644 --- a/sdk/python/flet/container.py +++ b/sdk/python/flet/container.py @@ -15,6 +15,7 @@ from flet.types import ( AnimationValue, BorderRadiusValue, + BoxShape, MarginValue, OffsetValue, PaddingValue, @@ -81,6 +82,7 @@ def __init__( image_repeat: ImageRepeat = None, image_fit: ImageFit = None, image_opacity: OptionalNumber = None, + shape: Optional[BoxShape] = None, clip_behavior: ClipBehavior = None, ink: Optional[bool] = None, animate: AnimationValue = None, @@ -136,6 +138,7 @@ def convert_container_tap_event_data(e): self.image_repeat = image_repeat self.image_fit = image_fit self.image_opacity = image_opacity + self.shape = shape self.clip_behavior = clip_behavior self.ink = ink self.animate = animate @@ -305,6 +308,16 @@ def content(self) -> Optional[Control]: def content(self, value: Optional[Control]): self.__content = value + # shape + @property + def shape(self): + return self._get_attr("shape") + + @shape.setter + @beartype + def shape(self, value: Optional[BoxShape]): + self._set_attr("shape", value.value if value is not None else None) + # clip_behavior @property def clip_behavior(self) -> Optional[ClipBehavior]: diff --git a/sdk/python/flet/drag_target.py b/sdk/python/flet/drag_target.py index 5b03e1abc2..348ea968d1 100644 --- a/sdk/python/flet/drag_target.py +++ b/sdk/python/flet/drag_target.py @@ -1,8 +1,11 @@ +import json from typing import Any, Optional from beartype import beartype from flet.control import Control +from flet.control_event import ControlEvent +from flet.event_handler import EventHandler from flet.ref import Ref @@ -31,6 +34,13 @@ def __init__( data=data, ) + def convert_accept_event_data(e): + d = json.loads(e.data) + return DragTargetAcceptEvent(**d) + + self.__on_accept = EventHandler(convert_accept_event_data) + self._add_event_handler("accept", self.__on_accept.handler) + self.__content: Optional[Control] = None self.group = group @@ -80,11 +90,11 @@ def on_will_accept(self, handler): # on_accept @property def on_accept(self): - return self._get_event_handler("accept") + return self.__on_accept @on_accept.setter def on_accept(self, handler): - self._add_event_handler("accept", handler) + self.__on_accept.subscribe(handler) # on_leave @property @@ -94,3 +104,10 @@ def on_leave(self): @on_leave.setter def on_leave(self, handler): self._add_event_handler("leave", handler) + + +class DragTargetAcceptEvent(ControlEvent): + def __init__(self, src_id, x, y) -> None: + self.src_id: float = src_id + self.x: float = x + self.y: float = y diff --git a/sdk/python/flet/dropdown.py b/sdk/python/flet/dropdown.py index c05c52e555..31f7a3a8af 100644 --- a/sdk/python/flet/dropdown.py +++ b/sdk/python/flet/dropdown.py @@ -6,6 +6,7 @@ from flet.focus import FocusData from flet.form_field_control import FormFieldControl from flet.ref import Ref +from flet.text_style import TextStyle from flet.types import ( AnimationValue, BorderRadiusValue, @@ -43,6 +44,7 @@ def __init__( # text_size: OptionalNumber = None, label: Optional[str] = None, + label_style: Optional[TextStyle] = None, icon: Optional[str] = None, border: InputBorder = None, color: Optional[str] = None, @@ -57,15 +59,21 @@ def __init__( content_padding: PaddingValue = None, filled: Optional[bool] = None, hint_text: Optional[str] = None, + hint_style: Optional[TextStyle] = None, helper_text: Optional[str] = None, + helper_style: Optional[TextStyle] = None, counter_text: Optional[str] = None, + counter_style: Optional[TextStyle] = None, error_text: Optional[str] = None, + error_style: Optional[TextStyle] = None, prefix: Optional[Control] = None, prefix_icon: Optional[str] = None, prefix_text: Optional[str] = None, + prefix_style: Optional[TextStyle] = None, suffix: Optional[Control] = None, suffix_icon: Optional[str] = None, suffix_text: Optional[str] = None, + suffix_style: Optional[TextStyle] = None, # # DropDown Specific # @@ -102,6 +110,7 @@ def __init__( # text_size=text_size, label=label, + label_style=label_style, icon=icon, border=border, color=color, @@ -116,15 +125,21 @@ def __init__( content_padding=content_padding, filled=filled, hint_text=hint_text, + hint_style=hint_style, helper_text=helper_text, + helper_style=helper_style, counter_text=counter_text, + counter_style=counter_style, error_text=error_text, + error_style=error_style, prefix=prefix, prefix_icon=prefix_icon, prefix_text=prefix_text, + prefix_style=prefix_style, suffix=suffix, suffix_icon=suffix_icon, suffix_text=suffix_text, + suffix_style=suffix_style, ) self.__options = [] diff --git a/sdk/python/flet/flet.py b/sdk/python/flet/flet.py index 9d2009620f..177950d925 100644 --- a/sdk/python/flet/flet.py +++ b/sdk/python/flet/flet.py @@ -407,7 +407,7 @@ def _open_flet_view(page_url, hidden): logging.info(f"Extracting Flet.app from archive to {temp_flet_dir}") temp_flet_dir.mkdir(parents=True, exist_ok=True) with tarfile.open(str(tar_file), "r:gz") as tar_arch: - tar_arch.extractall(str(temp_flet_dir)) + safe_tar_extractall(tar_arch, str(temp_flet_dir)) else: logging.info(f"Flet View found in: {temp_flet_dir}") @@ -428,7 +428,7 @@ def _open_flet_view(page_url, hidden): logging.info(f"Extracting Flet from archive to {temp_flet_dir}") temp_flet_dir.mkdir(parents=True, exist_ok=True) with tarfile.open(str(tar_file), "r:gz") as tar_arch: - tar_arch.extractall(str(temp_flet_dir)) + safe_tar_extractall(tar_arch, str(temp_flet_dir)) else: logging.info(f"Flet View found in: {temp_flet_dir}") @@ -479,7 +479,7 @@ def _download_fletd(): zip_arch.extractall(str(temp_fletd_dir)) else: with tarfile.open(temp_arch, "r:gz") as tar_arch: - tar_arch.extractall(str(temp_fletd_dir)) + safe_tar_extractall(tar_arch, str(temp_fletd_dir)) finally: os.remove(temp_arch) else: diff --git a/sdk/python/flet/form_field_control.py b/sdk/python/flet/form_field_control.py index c56e319749..3ea6a750b9 100644 --- a/sdk/python/flet/form_field_control.py +++ b/sdk/python/flet/form_field_control.py @@ -5,6 +5,7 @@ from flet.constrained_control import ConstrainedControl from flet.control import Control, InputBorder, OptionalNumber from flet.ref import Ref +from flet.text_style import TextStyle from flet.types import ( AnimationValue, BorderRadiusValue, @@ -46,6 +47,7 @@ def __init__( # text_size: OptionalNumber = None, label: Optional[str] = None, + label_style: Optional[TextStyle] = None, icon: Optional[str] = None, border: InputBorder = None, color: Optional[str] = None, @@ -60,15 +62,21 @@ def __init__( content_padding: PaddingValue = None, filled: Optional[bool] = None, hint_text: Optional[str] = None, + hint_style: Optional[TextStyle] = None, helper_text: Optional[str] = None, + helper_style: Optional[TextStyle] = None, counter_text: Optional[str] = None, + counter_style: Optional[TextStyle] = None, error_text: Optional[str] = None, + error_style: Optional[TextStyle] = None, prefix: Optional[Control] = None, prefix_icon: Optional[str] = None, prefix_text: Optional[str] = None, + prefix_style: Optional[TextStyle] = None, suffix: Optional[Control] = None, suffix_icon: Optional[str] = None, suffix_text: Optional[str] = None, + suffix_style: Optional[TextStyle] = None, ): ConstrainedControl.__init__( self, @@ -99,9 +107,17 @@ def __init__( self.__prefix: Optional[Control] = None self.__suffix: Optional[Control] = None + self.__label_style: Optional[TextStyle] = None + self.__hint_style: Optional[TextStyle] = None + self.__helper_style: Optional[TextStyle] = None + self.__counter_style: Optional[TextStyle] = None + self.__error_style: Optional[TextStyle] = None + self.__prefix_style: Optional[TextStyle] = None + self.__suffix_style: Optional[TextStyle] = None self.text_size = text_size self.label = label + self.label_style = label_style self.icon = icon self.border = border self.color = color @@ -116,20 +132,33 @@ def __init__( self.content_padding = content_padding self.filled = filled self.hint_text = hint_text + self.hint_style = hint_style self.helper_text = helper_text + self.helper_style = helper_style self.counter_text = counter_text + self.counter_style = counter_style self.error_text = error_text + self.error_style = error_style self.prefix = prefix self.prefix_icon = prefix_icon self.prefix_text = prefix_text + self.prefix_style = prefix_style self.suffix = suffix self.suffix_icon = suffix_icon self.suffix_text = suffix_text + self.suffix_style = suffix_style def _before_build_command(self): super()._before_build_command() self._set_attr_json("borderRadius", self.__border_radius) self._set_attr_json("contentPadding", self.__content_padding) + self._set_attr_json("labelStyle", self.__label_style) + self._set_attr_json("hintStyle", self.__hint_style) + self._set_attr_json("helperStyle", self.__helper_style) + self._set_attr_json("counterStyle", self.__counter_style) + self._set_attr_json("errorStyle", self.__error_style) + self._set_attr_json("prefixStyle", self.__prefix_style) + self._set_attr_json("suffixStyle", self.__suffix_style) def _get_children(self): children = [] @@ -160,6 +189,16 @@ def label(self): def label(self, value): self._set_attr("label", value) + # label_style + @property + def label_style(self): + return self.__label_style + + @label_style.setter + @beartype + def label_style(self, value: Optional[TextStyle]): + self.__label_style = value + # icon @property def icon(self): @@ -291,6 +330,16 @@ def hint_text(self): def hint_text(self, value): self._set_attr("hintText", value) + # hint_style + @property + def hint_style(self): + return self.__hint_style + + @hint_style.setter + @beartype + def hint_style(self, value: Optional[TextStyle]): + self.__hint_style = value + # helper_text @property def helper_text(self): @@ -300,6 +349,16 @@ def helper_text(self): def helper_text(self, value): self._set_attr("helperText", value) + # helper_style + @property + def helper_style(self): + return self.__helper_style + + @helper_style.setter + @beartype + def helper_style(self, value: Optional[TextStyle]): + self.__helper_style = value + # counter_text @property def counter_text(self): @@ -309,6 +368,16 @@ def counter_text(self): def counter_text(self, value): self._set_attr("counterText", value) + # counter_style + @property + def counter_style(self): + return self.__counter_style + + @counter_style.setter + @beartype + def counter_style(self, value: Optional[TextStyle]): + self.__counter_style = value + # error_text @property def error_text(self): @@ -318,6 +387,16 @@ def error_text(self): def error_text(self, value): self._set_attr("errorText", value) + # error_style + @property + def error_style(self): + return self.__error_style + + @error_style.setter + @beartype + def error_style(self, value: Optional[TextStyle]): + self.__error_style = value + # prefix @property def prefix(self): @@ -345,6 +424,16 @@ def prefix_text(self): def prefix_text(self, value): self._set_attr("prefixText", value) + # prefix_style + @property + def prefix_style(self): + return self.__prefix_style + + @prefix_style.setter + @beartype + def prefix_style(self, value: Optional[TextStyle]): + self.__prefix_style = value + # suffix @property def suffix(self): @@ -371,3 +460,13 @@ def suffix_text(self): @suffix_text.setter def suffix_text(self, value): self._set_attr("suffixText", value) + + # suffix_style + @property + def suffix_style(self): + return self.__suffix_style + + @suffix_style.setter + @beartype + def suffix_style(self, value: Optional[TextStyle]): + self.__suffix_style = value diff --git a/sdk/python/flet/gesture_detector.py b/sdk/python/flet/gesture_detector.py new file mode 100644 index 0000000000..ebe0b6a824 --- /dev/null +++ b/sdk/python/flet/gesture_detector.py @@ -0,0 +1,717 @@ +import json +from enum import Enum +from typing import Any, Optional, Union + +from beartype import beartype + +from flet.constrained_control import ConstrainedControl +from flet.control import Control, OptionalNumber +from flet.control_event import ControlEvent +from flet.event_handler import EventHandler +from flet.ref import Ref +from flet.types import AnimationValue, OffsetValue, RotateValue, ScaleValue + + +class MouseCursor(Enum): + ALIAS = "alias" + ALL_SCROLL = "allScroll" + BASIC = "basic" + CELL = "cell" + CLICK = "click" + CONTEXT_MENU = "contextMenu" + COPY = "copy" + DISAPPEARING = "disappearing" + FORBIDDEN = "forbidden" + GRAB = "grab" + GRABBING = "grabbing" + HELP = "help" + MOVE = "move" + NO_DROP = "noDrop" + NONE = "none" + PRECISE = "precise" + PROGRESS = "progress" + RESIZE_COLUMN = "ResizeColumn" + RESIZE_DOWN = "ResizeDown" + RESIZE_DOWN_LEFT = "ResizeDownLeft" + RESIZE_DOWN_RIGHT = "ResizeDownRight" + RESIZE_LEFT = "ResizeLeft" + RESIZE_LEFT_RIGHT = "ResizeLeftRight" + RESIZE_RIGHT = "ResizeRight" + RESIZE_ROW = "ResizeRow" + RESIZE_UP = "ResizeUp" + RESIZE_UP_DOWN = "ResizeUpDown" + RESIZE_UP_LEFT = "ResizeUpLeft" + RESIZE_UP_LEFT_DOWN_RIGHT = "ResizeUpLeftDownRight" + RESIZE_UP_RIGHT = "ResizeUpRight" + RESIZE_UP_RIGHT_DOWN_LEFT = "ResizeUpRightDownLeft" + TEXT = "text" + VERTICAL_TEXT = "verticalText" + WAIT = "wait" + ZOOM_IN = "zoomIn" + ZOOM_OUT = "zoomOut" + + +class GestureDetector(ConstrainedControl): + def __init__( + self, + content: Optional[Control] = None, + ref: Optional[Ref] = 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, + opacity: OptionalNumber = None, + rotate: RotateValue = None, + scale: ScaleValue = None, + offset: OffsetValue = 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, + visible: Optional[bool] = None, + disabled: Optional[bool] = None, + data: Any = None, + # + # Specific + # + mouse_cursor: Optional[MouseCursor] = None, + drag_interval: Optional[int] = None, + hover_interval: Optional[int] = None, + on_tap=None, + on_tap_down=None, + on_tap_up=None, + on_secondary_tap=None, + on_secondary_tap_down=None, + on_secondary_tap_up=None, + on_long_press_start=None, + on_long_press_end=None, + on_secondary_long_press_start=None, + on_secondary_long_press_end=None, + on_double_tap=None, + on_double_tap_down=None, + on_horizontal_drag_start=None, + on_horizontal_drag_update=None, + on_horizontal_drag_end=None, + on_vertical_drag_start=None, + on_vertical_drag_update=None, + on_vertical_drag_end=None, + on_pan_start=None, + on_pan_update=None, + on_pan_end=None, + on_scale_start=None, + on_scale_update=None, + on_scale_end=None, + on_hover=None, + on_enter=None, + on_exit=None, + ): + + ConstrainedControl.__init__( + self, + ref=ref, + width=width, + height=height, + left=left, + top=top, + right=right, + bottom=bottom, + expand=expand, + opacity=opacity, + rotate=rotate, + scale=scale, + offset=offset, + 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, + visible=visible, + disabled=disabled, + data=data, + ) + + self.__on_tap_down = EventHandler(lambda e: TapEvent(**json.loads(e.data))) + self._add_event_handler("tap_down", self.__on_tap_down.handler) + + self.__on_tap_up = EventHandler(lambda e: TapEvent(**json.loads(e.data))) + self._add_event_handler("tap_up", self.__on_tap_up.handler) + + self.__on_secondary_tap_down = EventHandler( + lambda e: TapEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "secondary_tap_down", self.__on_secondary_tap_down.handler + ) + + self.__on_secondary_tap_up = EventHandler( + lambda e: TapEvent(**json.loads(e.data)) + ) + self._add_event_handler("secondary_tap_up", self.__on_secondary_tap_up.handler) + + self.__on_long_press_start = EventHandler( + lambda e: LongPressStartEvent(**json.loads(e.data)) + ) + self._add_event_handler("long_press_start", self.__on_long_press_start.handler) + + self.__on_long_press_end = EventHandler( + lambda e: LongPressEndEvent(**json.loads(e.data)) + ) + self._add_event_handler("long_press_end", self.__on_long_press_end.handler) + + self.__on_secondary_long_press_start = EventHandler( + lambda e: LongPressStartEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "secondary_long_press_start", self.__on_secondary_long_press_start.handler + ) + + self.__on_secondary_long_press_end = EventHandler( + lambda e: LongPressEndEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "secondary_long_press_end", self.__on_secondary_long_press_end.handler + ) + self.__on_double_tap_down = EventHandler( + lambda e: TapEvent(**json.loads(e.data)) + ) + self._add_event_handler("double_tap_down", self.__on_double_tap_down.handler) + + # on_horizontal_drag + + self.__on_horizontal_drag_start = EventHandler( + lambda e: DragStartEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "horizontal_drag_start", self.__on_horizontal_drag_start.handler + ) + self.__on_horizontal_drag_update = EventHandler( + lambda e: DragUpdateEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "horizontal_drag_update", self.__on_horizontal_drag_update.handler + ) + self.__on_horizontal_drag_end = EventHandler( + lambda e: DragEndEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "horizontal_drag_end", self.__on_horizontal_drag_end.handler + ) + + # on_vertical_drag + + self.__on_vertical_drag_start = EventHandler( + lambda e: DragStartEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "vertical_drag_start", self.__on_vertical_drag_start.handler + ) + self.__on_vertical_drag_update = EventHandler( + lambda e: DragUpdateEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "vertical_drag_update", self.__on_vertical_drag_update.handler + ) + self.__on_vertical_drag_end = EventHandler( + lambda e: DragEndEvent(**json.loads(e.data)) + ) + self._add_event_handler( + "vertical_drag_end", self.__on_vertical_drag_end.handler + ) + + # on_pan + + self.__on_pan_start = EventHandler( + lambda e: DragStartEvent(**json.loads(e.data)) + ) + self._add_event_handler("pan_start", self.__on_pan_start.handler) + self.__on_pan_update = EventHandler( + lambda e: DragUpdateEvent(**json.loads(e.data)) + ) + self._add_event_handler("pan_update", self.__on_pan_update.handler) + self.__on_pan_end = EventHandler(lambda e: DragEndEvent(**json.loads(e.data))) + self._add_event_handler("pan_end", self.__on_pan_end.handler) + + # on_scale + + self.__on_scale_start = EventHandler( + lambda e: ScaleStartEvent(**json.loads(e.data)) + ) + self._add_event_handler("scale_start", self.__on_scale_start.handler) + self.__on_scale_update = EventHandler( + lambda e: ScaleUpdateEvent(**json.loads(e.data)) + ) + self._add_event_handler("scale_update", self.__on_scale_update.handler) + self.__on_scale_end = EventHandler( + lambda e: ScaleEndEvent(**json.loads(e.data)) + ) + self._add_event_handler("scale_end", self.__on_scale_end.handler) + + # on_hover + + self.__on_hover = EventHandler(lambda e: HoverEvent(**json.loads(e.data))) + self._add_event_handler("hover", self.__on_hover.handler) + self.__on_enter = EventHandler(lambda e: HoverEvent(**json.loads(e.data))) + self._add_event_handler("enter", self.__on_enter.handler) + self.__on_exit = EventHandler(lambda e: HoverEvent(**json.loads(e.data))) + self._add_event_handler("exit", self.__on_exit.handler) + + self.content = content + self.mouse_cursor = mouse_cursor + self.drag_interval = drag_interval + self.hover_interval = hover_interval + self.on_tap = on_tap + self.on_tap_down = on_tap_down + self.on_tap_up = on_tap_up + self.on_secondary_tap = on_secondary_tap + self.on_secondary_tap_down = on_secondary_tap_down + self.on_secondary_tap_up = on_secondary_tap_up + self.on_long_press_start = on_long_press_start + self.on_long_press_end = on_long_press_end + self.on_secondary_long_press_start = on_secondary_long_press_start + self.on_secondary_long_press_end = on_secondary_long_press_end + self.on_double_tap = on_double_tap + self.on_double_tap_down = on_double_tap_down + self.on_horizontal_drag_start = on_horizontal_drag_start + self.on_horizontal_drag_update = on_horizontal_drag_update + self.on_horizontal_drag_end = on_horizontal_drag_end + self.on_vertical_drag_start = on_vertical_drag_start + self.on_vertical_drag_update = on_vertical_drag_update + self.on_vertical_drag_end = on_vertical_drag_end + self.on_pan_start = on_pan_start + self.on_pan_update = on_pan_update + self.on_pan_end = on_pan_end + self.on_scale_start = on_scale_start + self.on_scale_update = on_scale_update + self.on_scale_end = on_scale_end + self.on_hover = on_hover + self.on_enter = on_enter + self.on_exit = on_exit + + def _get_control_name(self): + return "gesturedetector" + + def _get_children(self): + children = [] + if self.__content: + self.__content._set_attr_internal("n", "content") + children.append(self.__content) + return children + + # content + @property + def content(self) -> Optional[Control]: + return self.__content + + @content.setter + @beartype + def content(self, value: Optional[Control]): + self.__content = value + + # mouse_cursor + @property + def mouse_cursor(self): + return self._get_attr("mouseCursor") + + @mouse_cursor.setter + @beartype + def mouse_cursor(self, value: Optional[MouseCursor]): + self._set_attr("mouseCursor", value.value if value is not None else None) + + # drag_interval + @property + def drag_interval(self) -> Optional[int]: + return self._get_attr("dragInterval") + + @drag_interval.setter + @beartype + def drag_interval(self, value: Optional[int]): + self._set_attr("dragInterval", value) + + # hover_interval + @property + def hover_interval(self) -> Optional[int]: + return self._get_attr("hoverInterval") + + @hover_interval.setter + @beartype + def hover_interval(self, value: Optional[int]): + self._set_attr("hoverInterval", value) + + # on_tap + @property + def on_tap(self): + return self._get_event_handler("tap") + + @on_tap.setter + def on_tap(self, handler): + self._add_event_handler("tap", handler) + self._set_attr("onTap", True if handler is not None else None) + + # on_tap_down + @property + def on_tap_down(self): + return self.__on_tap_down + + @on_tap_down.setter + def on_tap_down(self, handler): + self.__on_tap_down.subscribe(handler) + self._set_attr("onTapDown", True if handler is not None else None) + + # on_tap_up + @property + def on_tap_up(self): + return self.__on_tap_up + + @on_tap_up.setter + def on_tap_up(self, handler): + self.__on_tap_up.subscribe(handler) + self._set_attr("onTapUp", True if handler is not None else None) + + # on_secondary_tap + @property + def on_secondary_tap(self): + return self._get_event_handler("secondary_tap") + + @on_secondary_tap.setter + def on_secondary_tap(self, handler): + self._add_event_handler("secondary_tap", handler) + self._set_attr("onSecondaryTap", True if handler is not None else None) + + # on_tap_down + @property + def on_secondary_tap_down(self): + return self.__on_secondary_tap_down + + @on_secondary_tap_down.setter + def on_secondary_tap_down(self, handler): + self.__on_secondary_tap_down.subscribe(handler) + self._set_attr("onSecondaryTapDown", True if handler is not None else None) + + # on_secondary_tap_up + @property + def on_secondary_tap_up(self): + return self.__on_secondary_tap_up + + @on_secondary_tap_up.setter + def on_secondary_tap_up(self, handler): + self.__on_secondary_tap_up.subscribe(handler) + self._set_attr("onSecondaryTapUp", True if handler is not None else None) + + # on_long_press_start + @property + def on_long_press_start(self): + return self.__on_long_press_start + + @on_long_press_start.setter + def on_long_press_start(self, handler): + self.__on_long_press_start.subscribe(handler) + self._set_attr("onLongPressStart", True if handler is not None else None) + + # on_long_press_end + @property + def on_long_press_end(self): + return self.__on_long_press_end + + @on_long_press_end.setter + def on_long_press_end(self, handler): + self.__on_long_press_end.subscribe(handler) + self._set_attr("onLongPressEnd", True if handler is not None else None) + + # on_secondary_long_press_start + @property + def on_secondary_long_press_start(self): + return self.__on_secondary_long_press_start + + @on_secondary_long_press_start.setter + def on_secondary_long_press_start(self, handler): + self.__on_secondary_long_press_start.subscribe(handler) + self._set_attr( + "onSecondaryLongPressStart", True if handler is not None else None + ) + + # on_secondary_long_press_end + @property + def on_secondary_long_press_end(self): + return self.__on_secondary_long_press_end + + @on_secondary_long_press_end.setter + def on_secondary_long_press_end(self, handler): + self.__on_secondary_long_press_end.subscribe(handler) + self._set_attr("onSecondaryLongPressEnd", True if handler is not None else None) + + # on_double_tap + @property + def on_double_tap(self): + return self._get_event_handler("double_tap") + + @on_double_tap.setter + def on_double_tap(self, handler): + self._add_event_handler("double_tap", handler) + self._set_attr("onDoubleTap", True if handler is not None else None) + + # on_double_tap_down + @property + def on_double_tap_down(self): + return self.__on_double_tap_down + + @on_double_tap_down.setter + def on_double_tap_down(self, handler): + self.__on_double_tap_down.subscribe(handler) + self._set_attr("onDoubleTapDown", True if handler is not None else None) + + # on_horizontal_drag_start + @property + def on_horizontal_drag_start(self): + return self.__on_horizontal_drag_start + + @on_horizontal_drag_start.setter + def on_horizontal_drag_start(self, handler): + self.__on_horizontal_drag_start.subscribe(handler) + self._set_attr("onHorizontalDragStart", True if handler is not None else None) + + # on_horizontal_drag_update + @property + def on_horizontal_drag_update(self): + return self.__on_horizontal_drag_update + + @on_horizontal_drag_update.setter + def on_horizontal_drag_update(self, handler): + self.__on_horizontal_drag_update.subscribe(handler) + self._set_attr("onHorizontalDragUpdate", True if handler is not None else None) + + # on_horizontal_drag_end + @property + def on_horizontal_drag_end(self): + return self.__on_horizontal_drag_end + + @on_horizontal_drag_end.setter + def on_horizontal_drag_end(self, handler): + self.__on_horizontal_drag_end.subscribe(handler) + self._set_attr("onHorizontalDragEnd", True if handler is not None else None) + + # on_vertical_drag_start + @property + def on_vertical_drag_start(self): + return self.__on_vertical_drag_start + + @on_vertical_drag_start.setter + def on_vertical_drag_start(self, handler): + self.__on_vertical_drag_start.subscribe(handler) + self._set_attr("onVerticalDragStart", True if handler is not None else None) + + # on_vertical_drag_update + @property + def on_vertical_drag_update(self): + return self.__on_vertical_drag_update + + @on_vertical_drag_update.setter + def on_vertical_drag_update(self, handler): + self.__on_vertical_drag_update.subscribe(handler) + self._set_attr("onVerticalDragUpdate", True if handler is not None else None) + + # on_vertical_drag_end + @property + def on_vertical_drag_end(self): + return self.__on_vertical_drag_end + + @on_vertical_drag_end.setter + def on_vertical_drag_end(self, handler): + self.__on_vertical_drag_end.subscribe(handler) + self._set_attr("onVerticalDragEnd", True if handler is not None else None) + + # on_pan_start + @property + def on_pan_start(self): + return self.__on_pan_start + + @on_pan_start.setter + def on_pan_start(self, handler): + self.__on_pan_start.subscribe(handler) + self._set_attr("onPanStart", True if handler is not None else None) + + # on_pan_updatevertical_drag + @property + def on_pan_update(self): + return self.__on_pan_update + + @on_pan_update.setter + def on_pan_update(self, handler): + self.__on_pan_update.subscribe(handler) + self._set_attr("onPanUpdate", True if handler is not None else None) + + # on_pan_end + @property + def on_pan_end(self): + return self.__on_pan_end + + @on_pan_end.setter + def on_pan_end(self, handler): + self.__on_pan_end.subscribe(handler) + self._set_attr("onPanEnd", True if handler is not None else None) + + # on_scale_start + @property + def on_scale_start(self): + return self.__on_scale_start + + @on_scale_start.setter + def on_scale_start(self, handler): + self.__on_scale_start.subscribe(handler) + self._set_attr("onScaleStart", True if handler is not None else None) + + # on_scale_update + @property + def on_scale_update(self): + return self.__on_scale_update + + @on_scale_update.setter + def on_scale_update(self, handler): + self.__on_scale_update.subscribe(handler) + self._set_attr("onScaleUpdate", True if handler is not None else None) + + # on_scale_end + @property + def on_scale_end(self): + return self.__on_scale_end + + @on_scale_end.setter + def on_scale_end(self, handler): + self.__on_scale_end.subscribe(handler) + self._set_attr("onScaleEnd", True if handler is not None else None) + + # on_hover + @property + def on_hover(self): + return self.__on_hover + + @on_hover.setter + def on_hover(self, handler): + self.__on_hover.subscribe(handler) + self._set_attr("onHover", True if handler is not None else None) + + # on_enter + @property + def on_enter(self): + return self.__on_enter + + @on_enter.setter + def on_enter(self, handler): + self.__on_enter.subscribe(handler) + self._set_attr("onEnter", True if handler is not None else None) + + # on_exit + @property + def on_exit(self): + return self.__on_exit + + @on_exit.setter + def on_exit(self, handler): + self.__on_exit.subscribe(handler) + self._set_attr("onExit", True if handler is not None else None) + + +class TapEvent(ControlEvent): + def __init__(self, lx, ly, gx, gy, kind) -> None: + self.local_x: float = lx + self.local_y: float = ly + self.global_x: float = gx + self.global_y: float = gy + self.kind: str = kind + + +class LongPressStartEvent(ControlEvent): + def __init__(self, lx, ly, gx, gy) -> None: + self.local_x: float = lx + self.local_y: float = ly + self.global_x: float = gx + self.global_y: float = gy + + +class LongPressEndEvent(ControlEvent): + def __init__(self, lx, ly, gx, gy, vx, vy) -> None: + self.local_x: float = lx + self.local_y: float = ly + self.global_x: float = gx + self.global_y: float = gy + self.velocity_x: float = vx + self.velocity_y: float = vy + + +class DragStartEvent(ControlEvent): + def __init__(self, lx, ly, gx, gy, kind, ts) -> None: + self.kind: str = kind + self.local_x: float = lx + self.local_y: float = ly + self.global_x: float = gx + self.global_y: float = gy + self.timestamp: Optional[int] = ts + + +class DragUpdateEvent(ControlEvent): + def __init__(self, dx, dy, pd, lx, ly, gx, gy, ts) -> None: + self.delta_x: float = dx + self.delta_y: float = dy + self.primary_delta: Optional[float] = pd + self.local_x: float = lx + self.local_y: float = ly + self.global_x: float = gx + self.global_y: float = gy + self.timestamp: Optional[int] = ts + + +class DragEndEvent(ControlEvent): + def __init__(self, pv, vx, vy) -> None: + self.primary_velocity: Optional[float] = pv + self.velocity_x: float = vx + self.velocity_y: float = vy + + +class ScaleStartEvent(ControlEvent): + def __init__(self, fpx, fpy, lfpx, lfpy, pc) -> None: + self.focal_point_x: float = fpx + self.focal_point_y: float = fpy + self.local_focal_point_x: float = lfpx + self.local_focal_point_y: float = lfpy + self.pointer_count: int = pc + + +class ScaleUpdateEvent(ControlEvent): + def __init__(self, fpx, fpy, fpdx, fpdy, lfpx, lfpy, pc, hs, vs, s, r) -> None: + self.focal_point_x: float = fpx + self.focal_point_y: float = fpy + self.focal_point_delta_x: float = fpdx + self.focal_point_delta_y: float = fpdy + self.local_focal_point_x: float = lfpx + self.local_focal_point_y: float = lfpy + self.pointer_count: int = pc + self.horizontal_scale: float = hs + self.vertical_scale: float = vs + self.scale: float = s + self.rotation: float = r + + +class ScaleEndEvent(ControlEvent): + def __init__(self, pc, vx, vy) -> None: + self.pointer_count: int = pc + self.velocity_x: float = vx + self.velocity_y: float = vy + + +class HoverEvent(ControlEvent): + def __init__(self, ts, kind, gx, gy, lx, ly, dx=None, dy=None) -> None: + self.timestamp: float = ts + self.kind: str = kind + self.global_x: float = gx + self.global_y: float = gy + self.local_x: float = lx + self.local_y: float = ly + self.delta_x: Optional[float] = dx + self.delta_y: Optional[float] = dy diff --git a/sdk/python/flet/page.py b/sdk/python/flet/page.py index c9af7c6373..e5eaa998ab 100644 --- a/sdk/python/flet/page.py +++ b/sdk/python/flet/page.py @@ -16,6 +16,7 @@ from flet.auth.oauth_provider import OAuthProvider from flet.banner import Banner from flet.client_storage import ClientStorage +from flet.clipboard import Clipboard from flet.connection import Connection from flet.control import ( Control, @@ -30,6 +31,7 @@ from flet.floating_action_button import FloatingActionButton from flet.protocol import Command from flet.pubsub import PubSub +from flet.querystring import QueryString from flet.session_storage import SessionStorage from flet.snack_bar import SnackBar from flet.theme import Theme @@ -41,8 +43,6 @@ except ImportError: from typing_extensions import Literal -from flet.querystring import QueryString - PageDesign = Literal[None, "material", "cupertino", "fluent", "macos", "adaptive"] ThemeMode = Literal[None, "system", "light", "dark"] @@ -123,6 +123,8 @@ def convert_keyboard_event(e): self._add_event_handler("connect", self.__on_connect.handler) self.__on_disconnect = EventHandler() self._add_event_handler("disconnect", self.__on_disconnect.handler) + self.__on_error = EventHandler() + self._add_event_handler("error", self.__on_error.handler) def __enter__(self): return self @@ -305,7 +307,6 @@ def go(self, route, **kwargs): self.update() self.query() # Update query url (required when using go) - def get_upload_url(self, file_name: str, expires: int): r = self._send_command( "getUploadUrl", attrs={"file": file_name, "expires": str(expires)} @@ -406,10 +407,10 @@ def _send_command( @beartype def set_clipboard(self, value: str): - self.invoke_method("setClipboard", {"value": value}) + self.__offstage.clipboard.set_data(value) def get_clipboard(self): - return self.invoke_method("getClipboard", wait_for_result=True) + return self.__offstage.clipboard.get_data() @beartype def launch_url( @@ -1178,6 +1179,15 @@ def on_logout(self): def on_logout(self, handler): self.__on_logout.subscribe(handler) + # on_error + @property + def on_error(self): + return self.__on_error + + @on_error.setter + def on_error(self, handler): + self.__on_error.subscribe(handler) + class Offstage(Control): def __init__( @@ -1195,6 +1205,7 @@ def __init__( ) self.__controls: List[Control] = [] + self.__clipboard = Clipboard() self.__banner = None self.__snack_bar = None self.__dialog = None @@ -1206,6 +1217,8 @@ def _get_control_name(self): def _get_children(self): children = [] children.extend(self.__controls) + if self.__clipboard: + children.append(self.__clipboard) if self.__banner: children.append(self.__banner) if self.__snack_bar: @@ -1221,6 +1234,11 @@ def _get_children(self): def controls(self): return self.__controls + # clipboard + @property + def clipboard(self): + return self.__clipboard + # splash @property def splash(self) -> Optional[Control]: diff --git a/sdk/python/flet/radio.py b/sdk/python/flet/radio.py index 51fc579242..9e318d3639 100644 --- a/sdk/python/flet/radio.py +++ b/sdk/python/flet/radio.py @@ -1,7 +1,9 @@ from typing import Any, Optional, Union from beartype import beartype +from beartype.typing import Dict +from flet.buttons import MaterialState from flet.constrained_control import ConstrainedControl from flet.control import OptionalNumber from flet.ref import Ref @@ -49,6 +51,7 @@ def __init__( label_position: LabelPosition = None, value: Optional[str] = None, autofocus: Optional[bool] = None, + fill_color: Union[None, str, Dict[MaterialState, str]] = None, on_focus=None, on_blur=None, ): @@ -82,12 +85,20 @@ def __init__( self.label = label self.label_position = label_position self.autofocus = autofocus + self.fill_color = fill_color self.on_focus = on_focus self.on_blur = on_blur def _get_control_name(self): return "radio" + def _before_build_command(self): + super()._before_build_command() + fc = self.__fill_color + if fc is not None and not isinstance(fc, Dict): + fc = {"": fc} + self._set_attr_json("fillColor", fc) + # value @property def value(self): @@ -116,6 +127,16 @@ def label_position(self) -> LabelPosition: def label_position(self, value: LabelPosition): self._set_attr("labelPosition", value) + # fill_color + @property + def fill_color(self) -> Union[None, str, Dict[MaterialState, str]]: + return self.__fill_color + + @fill_color.setter + @beartype + def fill_color(self, value: Union[None, str, Dict[MaterialState, str]]): + self.__fill_color = value + # on_focus @property def on_focus(self): diff --git a/sdk/python/flet/reconnecting_websocket.py b/sdk/python/flet/reconnecting_websocket.py index 33bc0e7278..e60b92925d 100644 --- a/sdk/python/flet/reconnecting_websocket.py +++ b/sdk/python/flet/reconnecting_websocket.py @@ -19,11 +19,6 @@ def __init__(self, url) -> None: self.connected = threading.Event() self.exit = threading.Event() self.retry = 0 - websocket.setdefaulttimeout( - _LOCAL_CONNECT_TIMEOUT_SEC - if is_localhost_url(url) - else _REMOTE_CONNECT_TIMEOUT_SEC - ) # disable websocket logging completely # https://github.com/websocket-client/websocket-client/blob/master/websocket/_logging.py#L22-L51 ws_logger = logging.getLogger("websocket") @@ -55,6 +50,7 @@ def on_message(self, handler): def _on_open(self, wsapp) -> None: logging.info(f"Successfully connected to {self._url}") + websocket.setdefaulttimeout(self.default_timeout) self.connected.set() self.retry = 0 if self._on_connect_handler is not None: @@ -80,13 +76,18 @@ def close(self) -> None: self.exit.set() self.wsapp.close() - # TODO: Can't do CTRL+C while it sleeps between re-connects - # Change to Event: https://stackoverflow.com/questions/5114292/break-interrupt-a-time-sleep-in-python def _connect_loop(self): while not self.exit.is_set(): logging.info(f"Connecting Flet Server at {self._url}...") + self.default_timeout = websocket.getdefaulttimeout() + websocket.setdefaulttimeout( + _LOCAL_CONNECT_TIMEOUT_SEC + if is_localhost_url(self._url) + else _REMOTE_CONNECT_TIMEOUT_SEC + ) r = self.wsapp.run_forever() logging.debug(f"Exited run_forever()") + websocket.setdefaulttimeout(self.default_timeout) self.connected.clear() if r != True: return diff --git a/sdk/python/flet/security.py b/sdk/python/flet/security.py index d1c464ae8b..74adbb555d 100644 --- a/sdk/python/flet/security.py +++ b/sdk/python/flet/security.py @@ -2,9 +2,12 @@ import hashlib import os -from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +try: + from cryptography.fernet import Fernet + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +except ImportError: + raise Exception('Install "cryptography" Python package to use Flet security utils.') def __generate_fernet_key(secret_key: str) -> bytes: diff --git a/sdk/python/flet/snack_bar.py b/sdk/python/flet/snack_bar.py index c4f2661358..dc7e4b651b 100644 --- a/sdk/python/flet/snack_bar.py +++ b/sdk/python/flet/snack_bar.py @@ -9,7 +9,7 @@ class SnackBar(Control): def __init__( self, - content: Optional[Control] = None, + content: Control, ref: Optional[Ref] = None, disabled: Optional[bool] = None, visible: Optional[bool] = None, @@ -20,6 +20,7 @@ def __init__( open: bool = False, # remove_current_snackbar: bool = False, action: Optional[str] = None, + action_color: Optional[str] = None, bgcolor: Optional[str] = None, on_action=None, ): @@ -36,6 +37,7 @@ def __init__( # self.remove_current_snackbar = remove_current_snackbar self.content = content self.action = action + self.action_color = action_color self.bgcolor = bgcolor self.on_action = on_action @@ -90,6 +92,15 @@ def action(self): def action(self, value): self._set_attr("action", value) + # action_color + @property + def action_color(self): + return self._get_attr("actionColor") + + @action_color.setter + def action_color(self, value): + self._set_attr("actionColor", value) + # bgcolor @property def bgcolor(self): diff --git a/sdk/python/flet/text.py b/sdk/python/flet/text.py index 862fa53209..9274bca6b7 100644 --- a/sdk/python/flet/text.py +++ b/sdk/python/flet/text.py @@ -5,28 +5,13 @@ from flet.constrained_control import ConstrainedControl from flet.control import OptionalNumber, TextAlign from flet.ref import Ref -from flet.types import AnimationValue, OffsetValue, RotateValue, ScaleValue +from flet.types import AnimationValue, FontWeight, OffsetValue, RotateValue, ScaleValue try: from typing import Literal except ImportError: from typing_extensions import Literal -FontWeight = Literal[ - None, - "normal", - "bold", - "w100", - "w200", - "w300", - "w400", - "w500", - "w600", - "w700", - "w800", - "w900", -] - TextOverflow = Literal[None, "clip", "ellipsis", "fade", "visible"] TextThemeStyle = Literal[ diff --git a/sdk/python/flet/text_style.py b/sdk/python/flet/text_style.py new file mode 100644 index 0000000000..0a4c41150a --- /dev/null +++ b/sdk/python/flet/text_style.py @@ -0,0 +1,14 @@ +import dataclasses +from dataclasses import field +from typing import Optional, Union + +from flet.types import FontWeight + + +@dataclasses.dataclass +class TextStyle: + size: Union[None, int, float] = field(default=None) + weight: FontWeight = field(default=None) + italic: Optional[bool] = field(default=None) + color: Optional[str] = field(default=None) + bgcolor: Optional[str] = field(default=None) diff --git a/sdk/python/flet/textfield.py b/sdk/python/flet/textfield.py index 08faa856de..9a95ca01ed 100644 --- a/sdk/python/flet/textfield.py +++ b/sdk/python/flet/textfield.py @@ -6,6 +6,7 @@ from flet.focus import FocusData from flet.form_field_control import FormFieldControl from flet.ref import Ref +from flet.text_style import TextStyle from flet.types import ( AnimationValue, BorderRadiusValue, @@ -65,6 +66,7 @@ def __init__( # text_size: OptionalNumber = None, label: Optional[str] = None, + label_style: Optional[TextStyle] = None, icon: Optional[str] = None, border: InputBorder = None, color: Optional[str] = None, @@ -79,15 +81,21 @@ def __init__( content_padding: PaddingValue = None, filled: Optional[bool] = None, hint_text: Optional[str] = None, + hint_style: Optional[TextStyle] = None, helper_text: Optional[str] = None, + helper_style: Optional[TextStyle] = None, counter_text: Optional[str] = None, + counter_style: Optional[TextStyle] = None, error_text: Optional[str] = None, + error_style: Optional[TextStyle] = None, prefix: Optional[Control] = None, prefix_icon: Optional[str] = None, prefix_text: Optional[str] = None, + prefix_style: Optional[TextStyle] = None, suffix: Optional[Control] = None, suffix_icon: Optional[str] = None, suffix_text: Optional[str] = None, + suffix_style: Optional[TextStyle] = None, # # TextField Specific # @@ -105,6 +113,9 @@ def __init__( autofocus: Optional[bool] = None, capitalization: TextCapitalization = None, cursor_color: Optional[str] = None, + cursor_width: OptionalNumber = None, + cursor_height: OptionalNumber = None, + cursor_radius: OptionalNumber = None, selection_color: Optional[str] = None, on_change=None, on_submit=None, @@ -137,6 +148,7 @@ def __init__( # text_size=text_size, label=label, + label_style=label_style, icon=icon, border=border, color=color, @@ -151,15 +163,21 @@ def __init__( content_padding=content_padding, filled=filled, hint_text=hint_text, + hint_style=hint_style, helper_text=helper_text, + helper_style=helper_style, counter_text=counter_text, + counter_style=counter_style, error_text=error_text, + error_style=error_style, prefix=prefix, prefix_icon=prefix_icon, prefix_text=prefix_text, + prefix_style=prefix_style, suffix=suffix, suffix_icon=suffix_icon, suffix_text=suffix_text, + suffix_style=suffix_style, ) self.value = value self.keyboard_type = keyboard_type @@ -175,6 +193,9 @@ def __init__( self.autofocus = autofocus self.capitalization = capitalization self.cursor_color = cursor_color + self.cursor_height = cursor_height + self.cursor_width = cursor_width + self.cursor_radius = cursor_radius self.selection_color = selection_color self.on_change = on_change self.on_submit = on_submit @@ -326,6 +347,36 @@ def cursor_color(self): def cursor_color(self, value): self._set_attr("cursorColor", value) + # cursor_height + @property + def cursor_height(self) -> OptionalNumber: + return self._get_attr("cursorHeight") + + @cursor_height.setter + @beartype + def cursor_height(self, value: OptionalNumber): + self._set_attr("cursorHeight", value) + + # cursor_width + @property + def cursor_width(self) -> OptionalNumber: + return self._get_attr("cursorWidth") + + @cursor_width.setter + @beartype + def cursor_width(self, value: OptionalNumber): + self._set_attr("cursorWidth", value) + + # cursor_radius + @property + def cursor_radius(self) -> OptionalNumber: + return self._get_attr("cursorRadius") + + @cursor_radius.setter + @beartype + def cursor_radius(self, value: OptionalNumber): + self._set_attr("cursorRadius", value) + # selection_color @property def selection_color(self): diff --git a/sdk/python/flet/types.py b/sdk/python/flet/types.py index f2dc68bcc7..8985016ded 100644 --- a/sdk/python/flet/types.py +++ b/sdk/python/flet/types.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Union from flet.animation import Animation @@ -6,6 +7,12 @@ from flet.padding import Padding from flet.transform import Offset, Rotate, Scale +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal + + PaddingValue = Union[None, int, float, Padding] MarginValue = Union[None, int, float, Margin] @@ -19,3 +26,23 @@ OffsetValue = Union[None, Offset] AnimationValue = Union[None, bool, int, Animation] + +FontWeight = Literal[ + None, + "normal", + "bold", + "w100", + "w200", + "w300", + "w400", + "w500", + "w600", + "w700", + "w800", + "w900", +] + + +class BoxShape(Enum): + RECTANGLE = "rectangle" + CIRCLE = "circle" diff --git a/sdk/python/flet/utils.py b/sdk/python/flet/utils.py index 4baff56cbd..8bda42f92a 100644 --- a/sdk/python/flet/utils.py +++ b/sdk/python/flet/utils.py @@ -76,6 +76,26 @@ def is_exe(fpath): return None +def is_within_directory(directory, target): + + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + + return prefix == abs_directory + + +def safe_tar_extractall(tar, path=".", members=None, *, numeric_owner=False): + + for member in tar.getmembers(): + member_path = os.path.join(path, member.name) + if not is_within_directory(path, member_path): + raise Exception("Attempted Path Traversal in Tar File") + + tar.extractall(path, members, numeric_owner=numeric_owner) + + def is_localhost_url(url): return ( "://localhost/" in url diff --git a/sdk/python/flet/version.py b/sdk/python/flet/version.py index 80d82ff094..e5d96807c5 100644 --- a/sdk/python/flet/version.py +++ b/sdk/python/flet/version.py @@ -1,13 +1,15 @@ -# this file will be replaced by CI - import subprocess as sp + from pkg_resources import parse_version from flet.utils import which +# this value will be replaced by CI +version = "" + def update_version(): - in_repo = which("git") and sp.run( + in_repo = sp.run( ["git", "status"], capture_output=True, text=True, @@ -22,11 +24,8 @@ def update_version(): text=True, ).stdout.splitlines() versions = filter(lambda t: t.startswith("v"), tags) - version = sorted(versions, key=parse_version)[-1][1:] - - else: - version = "0.1.60" # default to old version - return version + return sorted(versions, key=parse_version)[-1][1:] + return "0.1.60" if not globals().get("version", None): diff --git a/sdk/python/pdm.lock b/sdk/python/pdm.lock index 8ff90a11b7..a3e66ae8ea 100644 --- a/sdk/python/pdm.lock +++ b/sdk/python/pdm.lock @@ -22,14 +22,6 @@ version = "2022.6.15.1" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." -[[package]] -name = "cffi" -version = "1.15.1" -summary = "Foreign Function Interface for Python calling C code." -dependencies = [ - "pycparser", -] - [[package]] name = "cfgv" version = "3.3.1" @@ -48,15 +40,6 @@ version = "0.4.4" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" summary = "Cross-platform colored terminal text." -[[package]] -name = "cryptography" -version = "38.0.1" -requires_python = ">=3.6" -summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -dependencies = [ - "cffi>=1.12", -] - [[package]] name = "distlib" version = "0.3.5" @@ -151,12 +134,6 @@ version = "1.11.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" summary = "library with cross-python path, ini-parsing, io, code, log facilities" -[[package]] -name = "pycparser" -version = "2.21" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -summary = "C parser in Python" - [[package]] name = "pyparsing" version = "3.0.9" @@ -269,7 +246,7 @@ summary = "Backport of pathlib-compatible object wrapper for zip files" [metadata] lock_version = "3.1" -content_hash = "sha256:db9667e75da3e864823bf7a15c3776af680196e043761dbb1475c4b8e607e079" +content_hash = "sha256:9bd1e399fac3187f9ca477a6d26e125417df67a1172333ed5232019feb419eef" [metadata.files] "atomicwrites 1.4.0" = [ @@ -288,72 +265,6 @@ content_hash = "sha256:db9667e75da3e864823bf7a15c3776af680196e043761dbb1475c4b8e {file = "certifi-2022.6.15.1-py3-none-any.whl", hash = "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0"}, {file = "certifi-2022.6.15.1.tar.gz", hash = "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb"}, ] -"cffi 1.15.1" = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] "cfgv 3.3.1" = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, @@ -366,34 +277,6 @@ content_hash = "sha256:db9667e75da3e864823bf7a15c3776af680196e043761dbb1475c4b8e {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -"cryptography 38.0.1" = [ - {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"}, - {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"}, - {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"}, - {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"}, - {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"}, - {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"}, - {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"}, - {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"}, - {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"}, - {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"}, - {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"}, -] "distlib 0.3.5" = [ {file = "distlib-0.3.5-py2.py3-none-any.whl", hash = "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"}, {file = "distlib-0.3.5.tar.gz", hash = "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe"}, @@ -446,10 +329,6 @@ content_hash = "sha256:db9667e75da3e864823bf7a15c3776af680196e043761dbb1475c4b8e {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -"pycparser 2.21" = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] "pyparsing 3.0.9" = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 3f471f2984..fde2a04e8b 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -13,9 +13,7 @@ dependencies = [ "repath>=0.9.0", "watchdog>=2.1.9", "requests>=2.28.1", - "oauthlib>=3.2.0", - "cryptography>=38.0.1", -] + "oauthlib>=3.2.0"] requires-python = ">=3.7" license = { text = "MIT" } classifiers = [ diff --git a/server/page/client.go b/server/page/client.go index 026047aed0..31469316d7 100644 --- a/server/page/client.go +++ b/server/page/client.go @@ -254,7 +254,7 @@ func (c *Client) registerWebClientCore(request *RegisterWebClientRequestPayload) // lookup for existing session if request.SessionID != "" { - if sessionID, err := c.decryptSensitiveData(request.SessionID, c.clientIP); err == nil { + if sessionID, err := c.decryptSensitiveData(request.SessionID, ""); err == nil { session = store.GetSession(page, sessionID) } else { response.Error = fmt.Sprintf("error decrypting request.SessionID %s from %s: %s", request.SessionID, c.clientIP, err) @@ -310,7 +310,7 @@ func (c *Client) registerWebClientCore(request *RegisterWebClientRequestPayload) log.Debugf("Connected to zero session of %s page\n", page.Name) } - sessionID, err := c.encryptSensitiveData(session.ID, c.clientIP) + sessionID, err := c.encryptSensitiveData(session.ID, "") if err != nil { response.Error = fmt.Sprintf("error encrypting session.ID: %s", err) return