From ffec7a34e700fe1e6e108a7ce370d29a0ebea0d8 Mon Sep 17 00:00:00 2001 From: Nikita Sin <74384266+lesleysin@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:40:47 +0200 Subject: [PATCH] major: package v3, duit_kernel v3 migration (#145) * Migrate to kernel v3 API * Added re-export of kernel api, upd examples, export animation implementation parts, remove deprecations * Prepare package to publish, edit readme and changelog, upd example app * Upd example --- CHANGELOG.md | 13 + README.md | 50 ++-- example/ios/Runner/AppDelegate.swift | 2 +- example/lib/main.dart | 14 +- .../lib/src/custom/example/attributes.dart | 31 +- example/lib/src/custom/example/factories.dart | 11 +- example/lib/src/custom/example/widget.dart | 3 +- example/pubspec.lock | 22 +- example/pubspec.yaml | 2 +- lib/flutter_duit.dart | 17 +- lib/src/animations/animated_props.dart | 11 - lib/src/animations/animation_builder.dart | 5 +- lib/src/animations/animation_command.dart | 6 +- lib/src/animations/animation_context.dart | 1 - lib/src/animations/tween_description.dart | 3 - .../gesture_detector_attributes.dart | 42 ++- .../lifecycle_state_listener_attributes.dart | 12 +- lib/src/attributes/slider_attributes.dart | 6 +- lib/src/attributes/transform_attributes.dart | 2 +- lib/src/concurrency/distribution_policy.dart | 5 - lib/src/concurrency/distributor.dart | 36 --- lib/src/concurrency/index.dart | 4 - lib/src/concurrency/worker.dart | 112 -------- lib/src/concurrency/worker_pool.dart | 83 ------ lib/src/controller/view_controller.dart | 7 +- lib/src/duit_impl/driver.dart | 264 ++++++------------ lib/src/duit_impl/event.dart | 117 ++------ lib/src/duit_impl/hooks.dart | 1 - lib/src/duit_impl/host.dart | 3 - lib/src/duit_impl/subtree_holder.dart | 12 +- .../view_controller_change_listener.dart | 16 +- lib/src/kernel_api/index.dart | 17 ++ lib/src/transport/http.dart | 27 +- lib/src/transport/ws.dart | 28 +- lib/src/ui/models/element.dart | 12 +- lib/src/ui/models/element_models.dart | 2 +- lib/src/ui/models/ui_tree.dart | 6 +- lib/src/ui/widgets/transform.dart | 8 +- lib/src/ui/widgets/widget_fabric.dart | 2 +- lib/src/utils/detach_extension.dart | 7 - lib/src/utils/get_action.dart | 13 + lib/src/utils/index.dart | 3 +- lib/src/utils/json_utils.dart | 69 +---- lib/src/utils/parse_layout.dart | 4 +- pubspec.yaml | 2 +- 45 files changed, 339 insertions(+), 774 deletions(-) delete mode 100644 lib/src/concurrency/distribution_policy.dart delete mode 100644 lib/src/concurrency/distributor.dart delete mode 100644 lib/src/concurrency/index.dart delete mode 100644 lib/src/concurrency/worker.dart delete mode 100644 lib/src/concurrency/worker_pool.dart create mode 100644 lib/src/kernel_api/index.dart delete mode 100644 lib/src/utils/detach_extension.dart create mode 100644 lib/src/utils/get_action.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 58be2c2..c117a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 3.0.0 + +- Migration to duit_kernel v3 +- Removed deprecated features +- Removed DevMetrics feature (rework needed) +- Removed deprecated LayoutUpdate event handling +- Removed isolates support +- Improve library exports +- Update example app +- Action and events handling refactoring +- Re-exporting duit_kernel API as part of flutter_duit public API +- The Transform widget attribute type has been clarified + ## 2.3.0 - Added RotatedBox widget diff --git a/README.md b/README.md index 06850c1..86932bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Duit - drived UI tooklit. -***Duit*** is a backend side UI framework for Flutter. It is used for creating widgets and server-side state management. +***Duit*** is a backend side UI framework for Flutter. It is used for creating widgets and +server-side state management. The framework consists of several parts: @@ -8,20 +9,31 @@ The framework consists of several parts: - [Go backend adapter](https://github.com/lesleysin/duit_go) - [Node JS backend adapter](https://github.com/lesleysin/duit_js) -The framework ensures that the layout model is received from the server, interacts with the backend via the Action API, and embeds custom components into the widget hierarchy processing pipeline. Duit is flexible and extensible, which allows it to create rich UI dynamically. +The framework ensures that the layout model is received from the server, interacts with the backend +via the Action API, and embeds custom components into the widget hierarchy processing pipeline. Duit +is flexible and extensible, which allows it to create rich UI dynamically. ## Core features - Initial connection to the server and receiving a layout -- Support for different [network protocols](https://github.com/lesleysin/flutter_duit/wiki/Networking) (http, websocket) -- Pointed widget state update (updating only those widgets for which the server returned an "update") -- Actions API. A special protocol that allows the server to specify dependencies for an action associated with a widget. -- Ability to add your own [custom widgets](https://github.com/lesleysin/flutter_duit/wiki/Adding-custom-widgets) on the Flutter and backend side. +- Support for + different [network protocols](https://github.com/lesleysin/flutter_duit/wiki/Networking) (http, + websocket) +- Pointed widget state update (updating only those widgets for which the server returned an " + update") +- Actions API. A special protocol that allows the server to specify dependencies for an action + associated with a widget. +- Ability to add your + own [custom widgets](https://github.com/lesleysin/flutter_duit/wiki/Adding-custom-widgets) on the + Flutter and backend side. ## Matching library and kernel versions -The flutter_duit library depends on the [duit_kernel](https://github.com/lesleysin/duit_kernel) package, which contains basic model definitions for implementing framework entities and developing third-party packages and extensions. Carefully study the version compatibility table if you are going to directly add duit_kernel to your project (needed to implement custom widgets and extensions). - +The flutter_duit library depends on the [duit_kernel](https://github.com/lesleysin/duit_kernel) +package, which contains basic model definitions for implementing framework entities and developing +third-party packages and extensions. Carefully study the version compatibility table if you are +going to directly add duit_kernel to your project (needed to implement custom widgets and +extensions). | Lib versions | Kernel versions | |--------------|-----------------| @@ -34,21 +46,14 @@ The flutter_duit library depends on the [duit_kernel](https://github.com/lesleys | >= v2.0.0 | v2.0.1 | | >= v2.2.0 | v2.1.1 | | >= v2.3.0 | v2.1.3 | - +| >= v3.0.0 | v3.0.0 | ## Usage example -0. If you want to use the advanced features of the framework, you should install a dependency on the duit_kernel package, which provides definitions of the basic abstractions on the basis of which custom widgets and third-party extensions are implemented. - -``` -flutter pub add duit_kernel -``` +1. Create DuitDriver instance. -1. Create DuitDriver instance. - - -It is responsible for displaying the UI, updating the state of widgets, and calling widget-related actions. - +It is responsible for displaying the UI, updating the state of widgets, and calling widget-related +actions. ```dart @@ -67,17 +72,20 @@ final driver = DuitDriver( DuitViewHost( driver: driver, placeholder: const CircularProgressIndicator(), -), +) +, ``` ## Future plans + - Expanding the types of events generated by the server - Expanding the widget collection - Implementation of new network protocols (gRPC, socket.io) - Adding new adapters for the backend (Dart, C#, etc.) - Troubleshooting, updating documentation -## License +## License + MIT diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/lib/main.dart b/example/lib/main.dart index 8cabfd8..ad3e899 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,12 +2,11 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:duit_kernel/duit_kernel.dart'; +import 'package:dio/dio.dart'; +import 'package:example/src/custom/index.dart'; import 'package:flutter/material.dart'; import 'package:flutter_duit/flutter_duit.dart'; -import 'src/custom/index.dart'; - class CustomDecoder extends Converter> { @override Map convert(Uint8List input) { @@ -59,6 +58,13 @@ void main() async { attributesFactory: exAttributeFactory, ); + final dio = Dio(); + + final res = await dio.get("http://localhost:8999/components"); + + final comps = res.data!.cast>(); + await DuitRegistry.registerComponents(comps); + runApp(const MyApp()); } @@ -90,7 +96,7 @@ class _MyHomePageState extends State { @override void initState() { driver1 = DuitDriver( - "/some_endpoint", + "/example_screen", transportOptions: HttpTransportOptions( defaultHeaders: { "Content-Type": "application/json", diff --git a/example/lib/src/custom/example/attributes.dart b/example/lib/src/custom/example/attributes.dart index b2fe395..7f1a614 100644 --- a/example/lib/src/custom/example/attributes.dart +++ b/example/lib/src/custom/example/attributes.dart @@ -1,18 +1,31 @@ -import 'package:duit_kernel/duit_kernel.dart'; +import 'package:flutter_duit/flutter_duit.dart'; -class ExampleCustomWidgetAttributes +final class ExampleCustomWidgetAttributes extends AnimatedPropertyOwner implements DuitAttributes { - String? random; + final String? random; ExampleCustomWidgetAttributes({ required this.random, + required super.parentBuilderId, + required super.affectedProperties, }); + factory ExampleCustomWidgetAttributes.fromJson(Map json) { + final view = AnimatedPropHelper(json); + + return ExampleCustomWidgetAttributes( + random: view["random"], + parentBuilderId: view.parentBuilderId, + affectedProperties: view.affectedProperties, + ); + } + @override ExampleCustomWidgetAttributes copyWith(other) { return ExampleCustomWidgetAttributes( - random: other.random ?? random, - ); + random: other.random ?? random, + parentBuilderId: other.parentBuilderId ?? parentBuilderId, + affectedProperties: other.affectedProperties ?? affectedProperties); } @override @@ -21,7 +34,11 @@ class ExampleCustomWidgetAttributes Iterable? positionalParams, Map? namedParams, }) { - // TODO: implement dispatchInternalCall - throw UnimplementedError(); + return switch (methodName) { + "fromJson" => + ExampleCustomWidgetAttributes.fromJson(positionalParams!.first) + as ReturnT, + String() => throw UnimplementedError("$methodName is not implemented"), + }; } } diff --git a/example/lib/src/custom/example/factories.dart b/example/lib/src/custom/example/factories.dart index 7010404..da6ccf7 100644 --- a/example/lib/src/custom/example/factories.dart +++ b/example/lib/src/custom/example/factories.dart @@ -1,19 +1,18 @@ -import 'package:duit_kernel/duit_kernel.dart'; import 'package:example/src/custom/example/attributes.dart'; import 'package:example/src/custom/example/model.dart'; import 'package:example/src/custom/example/widget.dart'; import "package:flutter/material.dart"; +import 'package:flutter_duit/flutter_duit.dart'; DuitAttributes exAttributeFactory( String type, Map? json, ) { - return ExampleCustomWidgetAttributes(random: json?["random"] ?? "no random") - as DuitAttributes; + return ExampleCustomWidgetAttributes.fromJson(json ?? {}); } Widget exBuildFactory( - TreeElement model, [ + ElementTreeEntry model, [ Iterable subviews = const {}, ]) { final m = model as ExampleCustomWidget; @@ -29,12 +28,12 @@ Widget exBuildFactory( ); } -TreeElement exModelFactory( +ElementTreeEntry exModelFactory( String id, bool controlled, ViewAttribute attributes, UIElementController? controller, [ - Iterable subviews = const {}, + Iterable subviews = const {}, ]) { return ExampleCustomWidget( id: id, diff --git a/example/lib/src/custom/example/widget.dart b/example/lib/src/custom/example/widget.dart index c7a42a1..2423c65 100644 --- a/example/lib/src/custom/example/widget.dart +++ b/example/lib/src/custom/example/widget.dart @@ -1,4 +1,3 @@ -import "package:duit_kernel/duit_kernel.dart"; import "package:flutter/material.dart"; import "package:flutter_duit/flutter_duit.dart"; @@ -44,4 +43,4 @@ class _ExampleWidgetState extends State ), ); } -} \ No newline at end of file +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6211f2a..ea62d35 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,14 +49,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - duit_kernel: + dio: dependency: "direct main" + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + duit_kernel: + dependency: transitive description: name: duit_kernel - sha256: "80c2affe5fa0271c2fe17addbf5cd2f8d54f08596a370f6520bdf011744314af" + sha256: f813ecb708ceb114aa2757c121e7ba10275c73ea8d5233879d3b2f35c011a9bf url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.0.0" fake_async: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d8a2454..9baff8e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - duit_kernel: ^2.1.3 + dio: ^5.7.0 dev_dependencies: flutter_test: diff --git a/lib/flutter_duit.dart b/lib/flutter_duit.dart index 70bb476..8b92802 100644 --- a/lib/flutter_duit.dart +++ b/lib/flutter_duit.dart @@ -1,10 +1,13 @@ library flutter_duit; -export "./src/duit_impl/index.dart"; -export "./src/ui/index.dart"; -export "./src/transport/transport_type.dart"; -export "./src/transport/options.dart"; -export "./src/utils/index.dart" +export "package:flutter_duit/src/duit_impl/index.dart"; +export "package:flutter_duit/src/ui/index.dart"; +export "package:flutter_duit/src/transport/transport_type.dart"; +export "package:flutter_duit/src/transport/options.dart"; +export "package:flutter_duit/src/kernel_api/index.dart"; +export "package:flutter_duit/src/animations/index.dart" + show AnimatedPropHelper, AnimatedAttributes; +export "package:flutter_duit/src/utils/index.dart" hide GestureInterceptionLogic, AttributeParser, @@ -25,6 +28,4 @@ export "./src/utils/index.dart" ParseModelStartMessage, ParseModelEndMessage, RenderStartMessage, - RenderEndMessage, - DetachableController; -export './src/concurrency/index.dart' hide Worker; + RenderEndMessage; diff --git a/lib/src/animations/animated_props.dart b/lib/src/animations/animated_props.dart index ccd2064..20f4611 100644 --- a/lib/src/animations/animated_props.dart +++ b/lib/src/animations/animated_props.dart @@ -4,17 +4,6 @@ import 'package:flutter/material.dart'; import 'animation_context.dart'; mixin AnimatedAttributes on Widget { - @Deprecated("Will be removed in next major version") - T mergeWithController( - BuildContext context, - UIElementController controller, - ) { - return mergeWithAttributes( - context, - controller.attributes!.payload, - ); - } - /// Merges the [attributes] with the animated properties in the [DuitAnimationContext]. T mergeWithAttributes( BuildContext context, diff --git a/lib/src/animations/animation_builder.dart b/lib/src/animations/animation_builder.dart index c42ff86..289a128 100644 --- a/lib/src/animations/animation_builder.dart +++ b/lib/src/animations/animation_builder.dart @@ -28,7 +28,7 @@ class _DuitAnimationBuilderState extends State @override void initState() { widget.controller.listenCommand(_handleCommand); - final attrs = widget.controller.attributes!.payload; + final attrs = widget.controller.attributes.payload; for (var animation in attrs.tweenDescriptions) { final controller = AnimationController( vsync: this, @@ -200,7 +200,8 @@ class _DuitAnimationBuilderState extends State return DuitAnimationContext( data: dataObj, - parentId: wC.attributes?.payload.persistentId ?? wC.id, //Priority use of persistentId + parentId: wC.attributes.payload.persistentId ?? wC.id, + //Priority use of persistentId child: child ?? const SizedBox.shrink(), ); }, diff --git a/lib/src/animations/animation_command.dart b/lib/src/animations/animation_command.dart index 153a641..a86c87e 100644 --- a/lib/src/animations/animation_command.dart +++ b/lib/src/animations/animation_command.dart @@ -1,6 +1,10 @@ import 'index.dart'; - +/// A command that is sent to control an animation by interacting with an +/// animation controller identified by [controllerId]. The command specifies +/// the [method] to be executed, such as forward, reverse, repeat, or toggle, +/// on the animation controller. It also identifies the animated property key +/// using [animatedPropKey], which is affected by the animation. final class AnimationCommand { final String controllerId; final AnimationMethod method; diff --git a/lib/src/animations/animation_context.dart b/lib/src/animations/animation_context.dart index 810fda6..a211edf 100644 --- a/lib/src/animations/animation_context.dart +++ b/lib/src/animations/animation_context.dart @@ -1,6 +1,5 @@ import "package:flutter/material.dart"; - class DuitAnimationContext extends InheritedWidget { final Map data; final String parentId; diff --git a/lib/src/animations/tween_description.dart b/lib/src/animations/tween_description.dart index 5e458c0..6edc06b 100644 --- a/lib/src/animations/tween_description.dart +++ b/lib/src/animations/tween_description.dart @@ -1,4 +1,3 @@ -import 'package:duit_kernel/duit_kernel.dart'; import 'package:flutter/animation.dart'; import 'package:flutter_duit/src/utils/index.dart'; @@ -14,7 +13,6 @@ base class DuitTweenDescription { final AnimationTrigger trigger; final AnimationMethod method; final bool reverseOnRepeat; - final ServerAction? onAnimationEnd; DuitTweenDescription({ required this.animatedPropKey, @@ -25,7 +23,6 @@ base class DuitTweenDescription { this.curve = Curves.linear, this.method = AnimationMethod.forward, this.reverseOnRepeat = false, - this.onAnimationEnd, }); static AnimationMethod _methodFromValue(dynamic value) { diff --git a/lib/src/attributes/gesture_detector_attributes.dart b/lib/src/attributes/gesture_detector_attributes.dart index 8dfd19b..b8fbf9d 100644 --- a/lib/src/attributes/gesture_detector_attributes.dart +++ b/lib/src/attributes/gesture_detector_attributes.dart @@ -85,61 +85,59 @@ class GestureDetectorAttributes factory GestureDetectorAttributes.fromJson(JSONObject json) { return GestureDetectorAttributes( - onTap: - json["onTap"] != null ? ServerAction.fromJson(json["onTap"]) : null, + onTap: json["onTap"] != null ? ServerAction.parse(json["onTap"]) : null, onTapDown: json["onTapDown"] != null - ? ServerAction.fromJson(json["onTapDown"]) - : null, - onTapUp: json["onTapUp"] != null - ? ServerAction.fromJson(json["onTapUp"]) + ? ServerAction.parse(json["onTapDown"]) : null, + onTapUp: + json["onTapUp"] != null ? ServerAction.parse(json["onTapUp"]) : null, onTapCancel: json["onTapCancel"] != null - ? ServerAction.fromJson(json["onTapCancel"]) + ? ServerAction.parse(json["onTapCancel"]) : null, onDoubleTap: json["onDoubleTap"] != null - ? ServerAction.fromJson(json["onDoubleTap"]) + ? ServerAction.parse(json["onDoubleTap"]) : null, onDoubleTapDown: json["onDoubleTapDown"] != null - ? ServerAction.fromJson(json["onDoubleTapDown"]) + ? ServerAction.parse(json["onDoubleTapDown"]) : null, onDoubleTapCancel: json["onDoubleTapCancel"] != null - ? ServerAction.fromJson(json["onDoubleTapCancel"]) + ? ServerAction.parse(json["onDoubleTapCancel"]) : null, onLongPressDown: json["onLongPressDown"] != null - ? ServerAction.fromJson(json["onLongPressDown"]) + ? ServerAction.parse(json["onLongPressDown"]) : null, onLongPressCancel: json["onLongPressCancel"] != null - ? ServerAction.fromJson(json["onLongPressCancel"]) + ? ServerAction.parse(json["onLongPressCancel"]) : null, onLongPress: json["onLongPress"] != null - ? ServerAction.fromJson(json["onLongPress"]) + ? ServerAction.parse(json["onLongPress"]) : null, onLongPressStart: json["onLongPressStart"] != null - ? ServerAction.fromJson(json["onLongPressStart"]) + ? ServerAction.parse(json["onLongPressStart"]) : null, onLongPressMoveUpdate: json["onLongPressMoveUpdate"] != null - ? ServerAction.fromJson(json["onLongPressMoveUpdate"]) + ? ServerAction.parse(json["onLongPressMoveUpdate"]) : null, onLongPressUp: json["onLongPressUp"] != null - ? ServerAction.fromJson(json["onLongPressUp"]) + ? ServerAction.parse(json["onLongPressUp"]) : null, onLongPressEnd: json["onLongPressEnd"] != null - ? ServerAction.fromJson(json["onLongPressEnd"]) + ? ServerAction.parse(json["onLongPressEnd"]) : null, onPanStart: json["onPanStart"] != null - ? ServerAction.fromJson(json["onPanStart"]) + ? ServerAction.parse(json["onPanStart"]) : null, onPanDown: json["onPanDown"] != null - ? ServerAction.fromJson(json["onPanDown"]) + ? ServerAction.parse(json["onPanDown"]) : null, onPanUpdate: json["onPanUpdate"] != null - ? ServerAction.fromJson(json["onPanUpdate"]) + ? ServerAction.parse(json["onPanUpdate"]) : null, onPanEnd: json["onPanEnd"] != null - ? ServerAction.fromJson(json["onPanEnd"]) + ? ServerAction.parse(json["onPanEnd"]) : null, onPanCancel: json["onPanCancel"] != null - ? ServerAction.fromJson(json["onPanCancel"]) + ? ServerAction.parse(json["onPanCancel"]) : null, excludeFromSemantics: json['excludeFromSemantics'] ?? false, dragStartBehavior: diff --git a/lib/src/attributes/lifecycle_state_listener_attributes.dart b/lib/src/attributes/lifecycle_state_listener_attributes.dart index cf0a9a0..7eaf98e 100644 --- a/lib/src/attributes/lifecycle_state_listener_attributes.dart +++ b/lib/src/attributes/lifecycle_state_listener_attributes.dart @@ -46,22 +46,22 @@ class LifecycleStateListenerAttributes factory LifecycleStateListenerAttributes.fromJson(Map json) { return LifecycleStateListenerAttributes( onStateChanged: json['onStateChanged'] != null - ? ServerAction.fromJson(json['onStateChanged']) + ? ServerAction.parse(json['onStateChanged']) : null, onResumed: json['onResumed'] != null - ? ServerAction.fromJson(json['onResumed']) + ? ServerAction.parse(json['onResumed']) : null, onInactive: json['onInactive'] != null - ? ServerAction.fromJson(json['onInactive']) + ? ServerAction.parse(json['onInactive']) : null, onPaused: json['onPaused'] != null - ? ServerAction.fromJson(json['onPaused']) + ? ServerAction.parse(json['onPaused']) : null, onDetached: json['onDetached'] != null - ? ServerAction.fromJson(json['onDetached']) + ? ServerAction.parse(json['onDetached']) : null, onHidden: json['onHidden'] != null - ? ServerAction.fromJson(json['onHidden']) + ? ServerAction.parse(json['onHidden']) : null, ); } diff --git a/lib/src/attributes/slider_attributes.dart b/lib/src/attributes/slider_attributes.dart index 54644ba..cbc4962 100644 --- a/lib/src/attributes/slider_attributes.dart +++ b/lib/src/attributes/slider_attributes.dart @@ -49,13 +49,13 @@ final class SliderAttributes extends AttendedModel overlayColor: AttributeValueMapper.toMSPColor(json['overlayColor']), autofocus: json['autofocus'] ?? false, onChanged: json['onChanged'] != null - ? ServerAction.fromJson(json['onChanged']) + ? ServerAction.parse(json['onChanged']) : null, onChangeStart: json['onChangeStart'] != null - ? ServerAction.fromJson(json['onChangeStart']) + ? ServerAction.parse(json['onChangeStart']) : null, onChangeEnd: json['onChangeEnd'] != null - ? ServerAction.fromJson(json['onChangeEnd']) + ? ServerAction.parse(json['onChangeEnd']) : null, allowedInteraction: AttributeValueMapper.toSliderInteraction(json["allowedInteraction"]), diff --git a/lib/src/attributes/transform_attributes.dart b/lib/src/attributes/transform_attributes.dart index 7129435..35322ab 100644 --- a/lib/src/attributes/transform_attributes.dart +++ b/lib/src/attributes/transform_attributes.dart @@ -34,7 +34,7 @@ base class TransformAttributes extends AnimatedPropertyOwner { return FlipTransform.fromJson(data); default: return TransformAttributes( - type: 'none', parentBuilderId: '', affectedProperties: null); + type: 'none', parentBuilderId: '', affectedProperties: null,); } } } diff --git a/lib/src/concurrency/distribution_policy.dart b/lib/src/concurrency/distribution_policy.dart deleted file mode 100644 index 0cee5e5..0000000 --- a/lib/src/concurrency/distribution_policy.dart +++ /dev/null @@ -1,5 +0,0 @@ -@Deprecated("Will be removed in next major version") -enum TaskDistributionPolicy { - roundRobin, - leastConnection, -} diff --git a/lib/src/concurrency/distributor.dart b/lib/src/concurrency/distributor.dart deleted file mode 100644 index fc0e057..0000000 --- a/lib/src/concurrency/distributor.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:duit_kernel/duit_kernel.dart'; - -import 'index.dart'; - -@Deprecated("Will be removed in next major version") -abstract class Distributor { - Future distributeTask( - List workers, { - required TaskHandler fn, - dynamic payload, - }); -} - -@Deprecated("Will be removed in next major version") -final class RoundRobinDistributor extends Distributor { - int currentIndex = 0; - int? _ln; - - @override - Future distributeTask( - List workers, { - required TaskHandler fn, - dynamic payload, - }) async { - _ln ??= workers.length; - - if (_ln! - 1 == currentIndex) { - final worker = workers[currentIndex].sendTaskToIsolate(fn, payload); - currentIndex = 0; - return await worker; - } - final worker = workers[currentIndex].sendTaskToIsolate(fn, payload); - currentIndex++; - return await worker; - } -} diff --git a/lib/src/concurrency/index.dart b/lib/src/concurrency/index.dart deleted file mode 100644 index a5b2a6e..0000000 --- a/lib/src/concurrency/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'distributor.dart' hide RoundRobinDistributor; -export 'distribution_policy.dart'; -export 'worker_pool.dart'; -export 'worker.dart'; diff --git a/lib/src/concurrency/worker.dart b/lib/src/concurrency/worker.dart deleted file mode 100644 index 32ddbdf..0000000 --- a/lib/src/concurrency/worker.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:async'; -import 'dart:isolate'; - -import 'package:duit_kernel/duit_kernel.dart'; - -final class Worker { - final SendPort _sp; - final ReceivePort _rp; - final Isolate _isolate; - final Map> _activeRequests = {}; - bool _closed = false; - - Future sendTaskToIsolate( - TaskHandler fn, - dynamic payload, - ) async { - if (_closed) throw StateError('Worker closed'); - - final completer = Completer.sync(); - final cap = Capability(); - - final task = Task( - func: fn, - cap: cap, - payload: payload, - ); - - _activeRequests[cap] = completer; - - _sp.send(task); - return await completer.future; - } - - static Future spawn() async { - final initPort = RawReceivePort(); - final connection = Completer<(ReceivePort, SendPort)>.sync(); - initPort.handler = (initialMessage) { - final commandPort = initialMessage as SendPort; - connection.complete(( - ReceivePort.fromRawReceivePort(initPort), - commandPort, - )); - }; - - Isolate isolate; - - try { - isolate = await Isolate.spawn(_runIsolate, (initPort.sendPort)); - } on Object { - initPort.close(); - rethrow; - } - - final (ReceivePort receivePort, SendPort sendPort) = - await connection.future; - - return Worker._(receivePort, sendPort, isolate); - } - - Worker._( - this._rp, - this._sp, - this._isolate, - ) { - _rp.listen(_handleResponsesFromIsolate); - } - - void _handleResponsesFromIsolate(dynamic message) { - final response = message as TaskResult; - final completer = _activeRequests.remove(response.cap)!; - - if (response is RemoteError) { - completer.completeError(response); - } else { - completer.complete(response); - } - - if (_closed && _activeRequests.isEmpty) _rp.close(); - } - - static void _handleCommandsToIsolate( - ReceivePort receivePort, - SendPort sendPort, - ) { - receivePort.listen((event) async { - if (event is Task) { - final fnRes = event.func(event.payload); - final taskRes = TaskResult(fnRes, event.cap); - sendPort.send(taskRes); - return; - } - return; - }); - } - - static void _runIsolate(SendPort sendPort) { - final receivePort = ReceivePort(); - sendPort.send(receivePort.sendPort); - _handleCommandsToIsolate( - receivePort, - sendPort, - ); - } - - void dispose() { - if (!_closed) { - _closed = true; - if (_activeRequests.isEmpty) _rp.close(); - _isolate.kill(); - } - } -} diff --git a/lib/src/concurrency/worker_pool.dart b/lib/src/concurrency/worker_pool.dart deleted file mode 100644 index 8b142d4..0000000 --- a/lib/src/concurrency/worker_pool.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:async'; - -import 'package:duit_kernel/duit_kernel.dart'; -import 'package:flutter/foundation.dart'; - -import 'distributor.dart'; -import 'index.dart'; - -@Deprecated("Will be removed in next major version") -final class DuitWorkerPoolConfiguration extends WorkerPoolConfiguration { - final TaskDistributionPolicy policy; - - DuitWorkerPoolConfiguration({ - required super.workerCount, - required this.policy, - }); -} - -final class _WorkerPoolFinalizationController { - final WorkerPool p; - - _WorkerPoolFinalizationController(this.p); - - void dispose() { - p.dispose(); - } -} - -@Deprecated("Will be removed in next major version") -final class DuitWorkerPool extends WorkerPool { - late final Distributor _distributor; - final _workers = []; - - final Finalizer<_WorkerPoolFinalizationController> _workerPoolFinalizer = - Finalizer((wp) => wp.dispose()); - - @override - void dispose() { - for (var worker in _workers) { - worker.dispose(); - } - _workerPoolFinalizer.detach(this); - } - - @override - Future perform(TaskHandler func, dynamic payload) async { - return await _distributor.distributeTask( - _workers, - fn: func, - payload: payload, - ); - } - - @override - Future initWithConfiguration( - WorkerPoolConfiguration configuration, - ) async { - final config = configuration as DuitWorkerPoolConfiguration; - if (!initialized) { - for (var i = 0; i < config.workerCount; i++) { - try { - _workers.add(await Worker.spawn()); - } catch (e) { - debugPrint(e.toString()); - } - } - - _distributor = switch (config.policy) { - TaskDistributionPolicy.roundRobin => RoundRobinDistributor(), - TaskDistributionPolicy.leastConnection => throw UnimplementedError(), - }; - - _workerPoolFinalizer.attach( - this, - _WorkerPoolFinalizationController(this), - detach: this, - ); - initialized = true; - } else { - debugPrint("WorkerPool already initialized"); - } - } -} diff --git a/lib/src/controller/view_controller.dart b/lib/src/controller/view_controller.dart index 096c73f..7b95055 100644 --- a/lib/src/controller/view_controller.dart +++ b/lib/src/controller/view_controller.dart @@ -14,7 +14,7 @@ final class ViewController /// This property holds the attributes of the UI element that the `ViewController` controls. /// It can be used to access and modify the attributes of the UI element. @override - ViewAttribute? attributes; + ViewAttribute attributes; /// The server action associated with the UI element. /// @@ -58,8 +58,8 @@ final class ViewController required this.id, required this.driver, required this.type, + required this.attributes, this.action, - this.attributes, this.tag, }); @@ -115,8 +115,7 @@ final class ViewController } @override - void dispose() { + void detach() { driver.detachController(this.id); - super.dispose(); } } diff --git a/lib/src/duit_impl/driver.dart b/lib/src/duit_impl/driver.dart index 1a0cdcd..8ab060e 100644 --- a/lib/src/duit_impl/driver.dart +++ b/lib/src/duit_impl/driver.dart @@ -39,7 +39,7 @@ final class DuitDriver with DriverHooks implements UIDriver { @protected @override - StreamController streamController = + StreamController streamController = StreamController.broadcast(); @protected @@ -51,20 +51,7 @@ final class DuitDriver with DriverHooks implements UIDriver { buildContext = value; } - DuitAbstractTree? _layout; - - @override - @Deprecated("Will be removed in next major version") - bool concurrentModeEnabled; - - @protected - @override - WorkerPool? workerPool; - - @protected - @override - @Deprecated("Will be removed in next major version") - WorkerPoolConfiguration? workerPoolConfiguration; + ElementTree? _layout; final Finalizer<_DriverFinalizationController> _driverFinalizer = Finalizer((d) => d.dispose()); @@ -76,16 +63,16 @@ final class DuitDriver with DriverHooks implements UIDriver { @protected @override - DuitScriptRunner? scriptRunner; + ScriptRunner? scriptRunner; @override - Stream get stream => + Stream get stream => streamController.stream.asBroadcastStream(); @protected final Map? initialRequestPayload; - final bool _useStaticContent, _isModule, _devMetricsEnabled; + final bool _useStaticContent, _isModule; bool _isChannelInitialized = false; late final MethodChannel _driverChannel; @@ -97,29 +84,21 @@ final class DuitDriver with DriverHooks implements UIDriver { this.source, { required this.transportOptions, this.eventHandler, - this.concurrentModeEnabled = false, - this.workerPool, - this.workerPoolConfiguration, this.initialRequestPayload, bool enableDevMetrics = false, }) : _useStaticContent = false, - _isModule = false, - _devMetricsEnabled = enableDevMetrics; + _isModule = false; /// Creates a new instance of [DuitDriver] with the specified [content] without establishing a initial transport connection. DuitDriver.static( this.content, { required this.transportOptions, this.eventHandler, - this.concurrentModeEnabled = false, - this.workerPool, - this.workerPoolConfiguration, bool enableDevMetrics = false, }) : _useStaticContent = true, source = "", initialRequestPayload = null, - _isModule = false, - _devMetricsEnabled = enableDevMetrics; + _isModule = false; /// Creates a new [DuitDriver] instance that is controlled from native code DuitDriver.module() @@ -128,10 +107,8 @@ final class DuitDriver with DriverHooks implements UIDriver { initialRequestPayload = null, _isModule = true, eventHandler = null, - concurrentModeEnabled = false, transportOptions = EmptyTransportOptions(), - _driverChannel = const MethodChannel("duit:driver"), - _devMetricsEnabled = false; + _driverChannel = const MethodChannel("duit:driver"); // @@ -165,23 +142,11 @@ final class DuitDriver with DriverHooks implements UIDriver { Future init() async { onInit?.call(); - if (_devMetricsEnabled) { - DevMetrics().init(source); - } - if (_layout != null) { await Future.delayed(Duration.zero); streamController.sink.add(_layout); } else { - ViewAttribute.attributeParser = AttributeParser(); - - final wp = await _getWorkerPool(); - - if (wp != null && wp.initialized == false) { - assert( - workerPoolConfiguration != null, "Worker pool is not configured"); - await wp.initWithConfiguration(workerPoolConfiguration!); - } + _addParsers(); if (_isModule && !_isChannelInitialized) { await _initMethodChannel(); @@ -189,7 +154,6 @@ final class DuitDriver with DriverHooks implements UIDriver { transport ??= _getTransport( transportOptions.type, - workerPool: wp, ); await scriptRunner?.initWithTransport(transport!); @@ -199,7 +163,6 @@ final class DuitDriver with DriverHooks implements UIDriver { if (_useStaticContent) { json = content; } else { - DevMetrics().add(ConnectionStartMessage()); json = await transport?.connect( initialData: initialRequestPayload, ); @@ -245,6 +208,16 @@ final class DuitDriver with DriverHooks implements UIDriver { return _layout?.render(); } + void _addParsers() { + try { + ViewAttribute.attributeParser = AttributeParser(); + ServerEvent.eventParser = EventParser(); + } catch (_) { + //Safely handle the case of assigning parsers during + //multiple driver initializations as part of running tests + } + } + // // @@ -260,101 +233,84 @@ final class DuitDriver with DriverHooks implements UIDriver { /// /// Returns: A [Future] that completes with [void]. FutureOr _resolveEvent(dynamic eventData) async { - ServerEvent? event; + ServerEvent event; if (eventData is ServerEvent) { event = eventData; } else { - event = ServerEvent.fromJson(eventData, this); + event = ServerEvent.parseEvent(eventData); } onEventReceived?.call(event); - if (event != null) { - switch (event.type) { - case ServerEventType.update: - final updEvent = event as UpdateEvent; - updEvent.updates.forEach((key, value) async { - await _updateAttributes(key, value); - }); - break; - case ServerEventType.layoutUpdate: - final layoutUpdateEvent = event as LayoutUpdateEvent; - final newLayout = await layoutUpdateEvent.layout?.parse(); - - if (newLayout != null) { - _layout = newLayout; - streamController.sink.add(_layout); - } - - break; - case ServerEventType.navigation: - assert(eventHandler != null, "NavigationResolver is not set"); - final navEvent = event as NavigationEvent; - await eventHandler?.handleNavigation( + switch (event) { + case UpdateEvent(): + event.updates.forEach((key, value) async { + await _updateAttributes(key, value); + }); + break; + case NavigationEvent(): + assert( + eventHandler != null, "ExternalEventHandler instance is not set"); + await eventHandler?.handleNavigation( + buildContext, + event.path, + event.extra, + ); + break; + case OpenUrlEvent(): + assert( + eventHandler != null, "ExternalEventHandler instance is not set"); + await eventHandler?.handleOpenUrl(event.url); + break; + case CustomEvent(): + if (_isModule) { + await emitNativeEvent(event.key, event.extra); + } else { + await eventHandler?.handleCustomEvent( buildContext, - navEvent.path, - navEvent.extra, - ); - break; - case ServerEventType.openUrl: - assert(eventHandler != null, "NavigationResolver is not set"); - final urlEvent = event as OpenUrlEvent; - await eventHandler?.handleOpenUrl(urlEvent.url); - break; - case ServerEventType.custom: - final customEvent = event as CustomEvent; - if (_isModule) { - await emitNativeEvent(customEvent.key, customEvent.extra); - } else { - await eventHandler?.handleCustomEvent( - buildContext, - customEvent.key, - customEvent.extra, - ); - } - break; - case ServerEventType.sequenced: - final sequence = event as SequencedEventGroup; - for (final entry in sequence.events) { - await _resolveEvent(entry.event); - await Future.delayed(entry.delay); - } - break; - case ServerEventType.grouped: - final group = event as CommonEventGroup; - for (final entry in group.events) { - _resolveEvent(entry.event); - } - break; - case ServerEventType.animationTrigger: - final trigger = event as AnimationTriggerEvent; - await _resolveAnimationTrigger(trigger); - break; - case ServerEventType.timer: - final timerEvent = event as TimerEvent; - Timer( - timerEvent.timerDelay, - () async { - await _resolveEvent(timerEvent.event); - }, + event.key, + event.extra, ); - break; - } + } + break; + case SequencedEventGroup(): + for (final entry in event.events) { + await _resolveEvent(entry.event); + await Future.delayed(entry.delay); + } + break; + case CommonEventGroup(): + for (final entry in event.events) { + _resolveEvent(entry.event); + } + break; + case AnimationTriggerEvent(): + await _resolveAnimationTrigger(event); + break; + case TimerEvent(): + final evt = event; + Timer( + evt.timerDelay, + () async { + await _resolveEvent(evt.payload); + }, + ); + break; } onEventHandled?.call(); } - Map _preparePayload(List deps) { + Map _preparePayload(Iterable deps) { final Map payload = {}; if (deps.isNotEmpty) { for (final dependency in deps) { final controller = _viewControllers[dependency.id]; if (controller != null) { - if (controller.attributes?.payload is AttendedModel) { - final model = controller.attributes?.payload as AttendedModel; - payload[dependency.target] = model.collect(); + final attribute = controller.attributes.payload; + if (attribute is AttendedModel) { + payload[dependency.target] = attribute.collect(); } } } @@ -367,9 +323,9 @@ final class DuitDriver with DriverHooks implements UIDriver { Future execute(ServerAction action) async { beforeActionCallback?.call(action); - switch (action.executionType) { + switch (action) { //transport - case 0: + case TransportAction(): try { final payload = _preparePayload(action.dependsOn); @@ -384,26 +340,27 @@ final class DuitDriver with DriverHooks implements UIDriver { break; //local execution - case 1: + case LocalAction(): try { - await _resolveEvent(action.payload); + await _resolveEvent(action.event); } catch (e) { debugPrint(e.toString()); } break; //script - case 2: + case ScriptAction(): try { final body = _preparePayload(action.dependsOn); - final script = action.script as DuitScript; + final script = action.script; final scriptInvocationResult = await scriptRunner?.runScript( script.functionName, - url: action.event, - meta: action.script?.meta, + url: action.eventName, + meta: action.script.meta, body: body, ); - _resolveEvent(scriptInvocationResult); + + await _resolveEvent(scriptInvocationResult); } catch (e) { debugPrint(e.toString()); } @@ -430,25 +387,10 @@ final class DuitDriver with DriverHooks implements UIDriver { final description = DuitRegistry.getComponentDescription(tag!); if (description != null) { - Map component; - - if (concurrentModeEnabled) { - final response = await workerPool!.perform((params) { - return JsonUtils.fillComponentProperties( - params["layout"], - params["data"], - ); - }, { - "layout": description.data, - "data": json, - }); - component = response.result as Map; - } else { - component = JsonUtils.fillComponentProperties( - description.data, - json, - ); - } + final component = JsonUtils.mergeWithDataSource( + description, + json, + ); final attributes = ViewAttribute.createAttributes( ElementType.subtree, @@ -511,7 +453,7 @@ final class DuitDriver with DriverHooks implements UIDriver { /// Returns: /// - An instance of the transport class based on the specified [type]. /// - If the [type] is not recognized, it returns an instance of [HttpTransport]. - Transport _getTransport(String type, {WorkerPool? workerPool}) { + Transport _getTransport(String type) { if (_isModule) { return NativeTransport(this); } @@ -521,8 +463,6 @@ final class DuitDriver with DriverHooks implements UIDriver { { return HttpTransport( source, - concurrencyEnabled: concurrentModeEnabled, - workerPool: workerPool, options: transportOptions as HttpTransportOptions, ); } @@ -530,8 +470,6 @@ final class DuitDriver with DriverHooks implements UIDriver { { return WSTransport( source, - workerPool: workerPool, - concurrencyEnabled: concurrentModeEnabled, options: transportOptions as WebSocketTransportOptions, ); } @@ -539,30 +477,12 @@ final class DuitDriver with DriverHooks implements UIDriver { { return HttpTransport( source, - concurrencyEnabled: concurrentModeEnabled, - workerPool: workerPool, options: transportOptions as HttpTransportOptions, ); } } } - Future _getWorkerPool() async { - if (concurrentModeEnabled) { - if (workerPool != null) { - return workerPool!; - } - - final sharedPool = DuitRegistry.workerPool(); - if (sharedPool != null) { - return workerPool = sharedPool; - } - - return null; - } - return null; - } - /// Initializes the driver as a module. Future _initMethodChannel() async { _driverChannel.setMethodCallHandler((call) async { diff --git a/lib/src/duit_impl/event.dart b/lib/src/duit_impl/event.dart index ff226df..028c31d 100644 --- a/lib/src/duit_impl/event.dart +++ b/lib/src/duit_impl/event.dart @@ -1,42 +1,15 @@ -import 'dart:convert'; - import 'package:duit_kernel/duit_kernel.dart'; import 'package:flutter_duit/src/animations/animation_command.dart'; import 'package:flutter_duit/src/animations/animation_method.dart'; -import 'package:flutter_duit/src/ui/models/ui_tree.dart'; import 'package:flutter_duit/src/utils/index.dart'; -/// Represents the type of a server event. -enum ServerEventType { - update, - layoutUpdate, - navigation, - openUrl, - custom, - sequenced, - grouped, - animationTrigger, - timer, -} - -/// Represents a server response event. -abstract class ServerEvent { - /// The type of the server event. - abstract ServerEventType type; - - /// Creates a [ServerEvent] object from a JSON object. - /// - /// The [json] parameter is a JSON object representing the server event. - /// Returns a [ServerEvent] object if the JSON object is valid and represents - /// a recognized server event type, otherwise returns `null`. - static ServerEvent? fromJson(JSONObject? json, UIDriver driver) { - if (json == null) return null; - +final class EventParser implements Parser { + @override + ServerEvent parse(JSONObject json) { final type = json["type"]; return switch (type) { "update" => UpdateEvent.fromJson(json), - "updateLayout" => LayoutUpdateEvent.fromJson(json, driver), "navigation" => NavigationEvent.fromJson(json), "openUrl" => OpenUrlEvent.fromJson(json), "custom" => CustomEvent.fromJson(json), @@ -44,16 +17,17 @@ abstract class ServerEvent { "grouped" => CommonEventGroup.fromJson(json), "animationTrigger" => AnimationTriggerEvent.fromJson(json), "timer" => TimerEvent.fromJson(json), - String() || Object() || null => null, + String() || Object() || null => NullEvent(), }; } } +final class NullEvent extends ServerEvent { + NullEvent() : super(type: "null_event"); +} + /// Represents an update event. final class UpdateEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.update; - /// The updates associated with the event. Map updates; @@ -62,7 +36,7 @@ final class UpdateEvent extends ServerEvent { /// The [updates] parameter is a map of key-value pairs representing the updates. UpdateEvent({ required this.updates, - }); + }) : super(type: "update"); /// Creates an [UpdateEvent] object from a JSON object. /// @@ -73,31 +47,7 @@ final class UpdateEvent extends ServerEvent { } } -final class LayoutUpdateEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.layoutUpdate; - - DuitAbstractTree? layout; - - LayoutUpdateEvent(this.layout); - - /// Creates an [LayoutUpdateEvent] object from a JSON object. - /// - /// The [json] parameter is a JSON object representing the update event. - /// Returns an [LayoutUpdateEvent] object if the JSON object is valid, otherwise throws an exception. - factory LayoutUpdateEvent.fromJson(JSONObject json, UIDriver driver) { - final layout = DuitTree( - json: json is String ? jsonDecode(json["layout"]) : json["layout"], - driver: driver, - ); - return LayoutUpdateEvent(layout); - } -} - final class NavigationEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.navigation; - final String path; final Map extra; @@ -105,7 +55,7 @@ final class NavigationEvent extends ServerEvent { NavigationEvent({ required this.path, required this.extra, - }); + }) : super(type: "navigation"); factory NavigationEvent.fromJson(JSONObject json) { return NavigationEvent( @@ -116,14 +66,11 @@ final class NavigationEvent extends ServerEvent { } final class OpenUrlEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.openUrl; - final String url; OpenUrlEvent({ required this.url, - }); + }) : super(type: "openUrl"); factory OpenUrlEvent.fromJson(JSONObject json) { return OpenUrlEvent( @@ -133,9 +80,6 @@ final class OpenUrlEvent extends ServerEvent { } final class CustomEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.custom; - final String key; final Map extra; @@ -143,7 +87,7 @@ final class CustomEvent extends ServerEvent { CustomEvent({ required this.key, required this.extra, - }); + }) : super(type: "custom"); factory CustomEvent.fromJson(JSONObject json) { return CustomEvent( @@ -154,7 +98,7 @@ final class CustomEvent extends ServerEvent { } final class GroupMember { - final JSONObject event; + final ServerEvent event; GroupMember({ required this.event, @@ -173,17 +117,16 @@ final class SequencedGroupMember extends GroupMember { final class CommonEventGroup extends ServerEvent { final List events; - @override - ServerEventType type = ServerEventType.grouped; - CommonEventGroup({ required this.events, - }); + }) : super(type: "grouped"); factory CommonEventGroup.fromJson(JSONObject json) { final list = List.from(json["events"] ?? []); - final events = list.map((model) => GroupMember(event: model)); + final events = list.map( + (model) => GroupMember(event: ServerEvent.parseEvent(model)), + ); return CommonEventGroup(events: events.toList()); } } @@ -191,12 +134,9 @@ final class CommonEventGroup extends ServerEvent { final class SequencedEventGroup extends ServerEvent { final List events; - @override - ServerEventType type = ServerEventType.sequenced; - SequencedEventGroup({ required this.events, - }); + }) : super(type: "sequenced"); factory SequencedEventGroup.fromJson(JSONObject json) { final list = List.from(json["events"] ?? []); @@ -204,21 +144,21 @@ final class SequencedEventGroup extends ServerEvent { final events = list.map((model) { final delay = Duration(milliseconds: model["delay"] ?? 0); - return SequencedGroupMember(event: model["event"], delay: delay); + return SequencedGroupMember( + event: ServerEvent.parseEvent(model["event"]), + delay: delay, + ); }); return SequencedEventGroup(events: events.toList()); } } final class AnimationTriggerEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.animationTrigger; - final AnimationCommand command; AnimationTriggerEvent({ required this.command, - }); + }) : super(type: "animationTrigger"); factory AnimationTriggerEvent.fromJson(JSONObject json) { return AnimationTriggerEvent( @@ -238,21 +178,18 @@ final class AnimationTriggerEvent extends ServerEvent { } final class TimerEvent extends ServerEvent { - @override - ServerEventType type = ServerEventType.timer; - final Duration timerDelay; - final JSONObject event; + final ServerEvent payload; TimerEvent({ required this.timerDelay, - required this.event, - }); + required this.payload, + }) : super(type: "timer"); factory TimerEvent.fromJson(JSONObject json) { return TimerEvent( timerDelay: Duration(milliseconds: json["timerDelay"] ?? 0), - event: json["event"] ?? {}, + payload: ServerEvent.parseEvent(json["event"]), ); } } diff --git a/lib/src/duit_impl/hooks.dart b/lib/src/duit_impl/hooks.dart index a830575..e572749 100644 --- a/lib/src/duit_impl/hooks.dart +++ b/lib/src/duit_impl/hooks.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:duit_kernel/duit_kernel.dart'; -import 'package:flutter_duit/src/duit_impl/event.dart'; ///A mixin that adds driver definitions for hook ///functions that will be called during certain events diff --git a/lib/src/duit_impl/host.dart b/lib/src/duit_impl/host.dart index 8ba4955..2f78769 100644 --- a/lib/src/duit_impl/host.dart +++ b/lib/src/duit_impl/host.dart @@ -105,10 +105,7 @@ class _DuitViewHostState extends State { child: _StackWrapper( invertStack: widget.invertStack, content: () { - final devM = DevMetrics(); - devM.add(RenderStartMessage()); final content = snapshot.data!.render(); - devM.add(RenderEndMessage()); return content; }(), child: widget.child, diff --git a/lib/src/duit_impl/subtree_holder.dart b/lib/src/duit_impl/subtree_holder.dart index 782825a..51ab11e 100644 --- a/lib/src/duit_impl/subtree_holder.dart +++ b/lib/src/duit_impl/subtree_holder.dart @@ -13,7 +13,7 @@ mixin SubtreeHolder on State { } Future _listener() async { - final layout = _controller?.attributes?.payload as SubtreeAttributes?; + final layout = _controller?.attributes.payload as SubtreeAttributes?; if (layout?.data != null) { final driver = _controller!.driver; @@ -41,12 +41,10 @@ mixin SubtreeHolder on State { @override void dispose() { - if (_controller != null) { - final controller = DetachableController(_controller!); - controller - ..removeListener(_listener) - ..detach(); - } + _controller + ?..removeListener(_listener) + ..detach(); + super.dispose(); } } diff --git a/lib/src/duit_impl/view_controller_change_listener.dart b/lib/src/duit_impl/view_controller_change_listener.dart index 3f38152..275a6e7 100644 --- a/lib/src/duit_impl/view_controller_change_listener.dart +++ b/lib/src/duit_impl/view_controller_change_listener.dart @@ -1,6 +1,5 @@ import 'package:duit_kernel/duit_kernel.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_duit/src/utils/detach_extension.dart'; /// A mixin that listens for changes in a UI element controller and updates the state accordingly. /// @@ -62,18 +61,16 @@ mixin ViewControllerChangeListener /// ``` void attachStateToController(UIElementController controller) { _controller = controller; - attributes = _controller.attributes!.payload; + attributes = _controller.attributes.payload; } void _listener() { - final newState = _controller.attributes?.cast(); + final newState = _controller.attributes.cast(); final attrs = attributes as DuitAttributes; - if (newState != null) { - setState(() { - attributes = attrs.copyWith(newState.payload); - }); - } + setState(() { + attributes = attrs.copyWith(newState.payload); + }); } /// Updates the state of the `UIElementController` manually. @@ -99,8 +96,7 @@ mixin ViewControllerChangeListener @override void dispose() { - final controller = DetachableController(_controller); - controller + _controller ..removeListener(_listener) ..detach(); super.dispose(); diff --git a/lib/src/kernel_api/index.dart b/lib/src/kernel_api/index.dart new file mode 100644 index 0000000..2744843 --- /dev/null +++ b/lib/src/kernel_api/index.dart @@ -0,0 +1,17 @@ +export 'package:duit_kernel/duit_kernel.dart' + show + DuitRegistry, + DuitAttributes, + ElementTree, + ElementTreeEntry, + AttributesFactory, + ModelFactory, + BuildFactory, + Transport, + TransportOptions, + Streamer, + AnimatedPropertyOwner, + ScriptRunner, + UIDriver, + UIElementController, + ViewAttribute; diff --git a/lib/src/transport/http.dart b/lib/src/transport/http.dart index 532de7b..4f5b31c 100644 --- a/lib/src/transport/http.dart +++ b/lib/src/transport/http.dart @@ -27,14 +27,10 @@ import 'transport_utils.dart'; final class HttpTransport extends Transport { final client = http.Client(); final HttpTransportOptions options; - final bool concurrencyEnabled; - final _dm = DevMetrics(); HttpTransport( super.url, { - super.workerPool, required this.options, - required this.concurrencyEnabled, }); String _prepareUrl(String url) { @@ -83,9 +79,7 @@ final class HttpTransport extends Transport { ///Send request and await for response final response = await request.send(); - _dm.add(ReqStartMessage()); final res = await response.stream.toBytes(); - _dm.add(ReqEndMessage()); return await _parseResponse(res); } catch (e) { @@ -96,42 +90,27 @@ final class HttpTransport extends Transport { } Future?> _parseResponse(Uint8List data) async { - _dm.add(DecodeStartMessage()); - if (options.decoder != null) { final decodingResult = options.decoder!.convert(data) as Map; - _dm.add(DecodeEndMessage()); return decodingResult; } - ///Check if concurrency is enabled and run the task in isolate - if (concurrencyEnabled && workerPool != null) { - final taskResult = await workerPool!.perform( - (params) { - return jsonDecode(params); - }, - utf8.decode(data), - ); - _dm.add(DecodeEndMessage()); - return taskResult.result as Map; - } - - _dm.add(DecodeEndMessage()); - ///If concurrency is not enabled, run the task in main isolate return jsonDecode(utf8.decode(data)) as Map; } @override FutureOr execute(action, payload) async { + if (action is! TransportAction) return null; + ///Prepare url and method String method = switch (action.meta) { null => "GET", HttpActionMetainfo() => action.meta!.method, }; - final urlString = _prepareUrl(action.event); + final urlString = _prepareUrl(action.eventName); Uri uri; final reqInter = options.requestInterceptor; final errInter = options.errorInterceptor; diff --git a/lib/src/transport/ws.dart b/lib/src/transport/ws.dart index 461bbe6..a46b713 100644 --- a/lib/src/transport/ws.dart +++ b/lib/src/transport/ws.dart @@ -28,8 +28,6 @@ final class WSTransport extends Transport implements Streamer { StreamController>(); late final WebSocket ws; final WebSocketTransportOptions options; - final bool concurrencyEnabled; - final _dm = DevMetrics(); @override Stream> get eventStream => @@ -37,9 +35,7 @@ final class WSTransport extends Transport implements Streamer { WSTransport( super.url, { - super.workerPool, required this.options, - required this.concurrencyEnabled, }); String _prepareUrl(String url) { @@ -51,23 +47,8 @@ final class WSTransport extends Transport implements Streamer { return urlString += url; } - Future> _parseJson(String data) async { - _dm.add(DecodeStartMessage()); - - if (concurrencyEnabled && workerPool != null) { - final tres = await workerPool!.perform( - (params) { - return jsonDecode(params); - }, - data, - ); - _dm.add(DecodeEndMessage()); - return tres.result as Map; - } - - _dm.add(DecodeEndMessage()); - return jsonDecode(data) as Map; - } + Future> _parseJson(String data) async => + jsonDecode(data) as Map; @override Future?> connect({ @@ -82,8 +63,6 @@ final class WSTransport extends Transport implements Streamer { assert(urlString.isNotEmpty && urlString.startsWith("ws"), "Invalid url: $url}"); - _dm.add(ReqStartMessage()); - ws = await WebSocket.connect( urlString, headers: options.defaultHeaders, @@ -91,7 +70,6 @@ final class WSTransport extends Transport implements Streamer { ws.listen( (event) async { - _dm.add(ReqEndMessage()); final data = await _parseJson(event); _controller.sink.add(data); }, @@ -113,7 +91,7 @@ final class WSTransport extends Transport implements Streamer { @override FutureOr execute(action, payload) { final data = { - "event": action.event, + "event": action.eventName, "payload": payload, }; diff --git a/lib/src/ui/models/element.dart b/lib/src/ui/models/element.dart index 03667c8..ae6b45b 100644 --- a/lib/src/ui/models/element.dart +++ b/lib/src/ui/models/element.dart @@ -13,7 +13,7 @@ import 'element_type.dart'; /// The [DuitElement] class represents an individual DUIT element in the DUIT element tree. /// It holds information about the element's type, properties, and child elements. /// The [DuitElement] class provides methods for rendering the element to a Flutter widget and handling interactions. -base class DuitElement extends TreeElement with WidgetFabric { +base class DuitElement extends ElementTreeEntry with WidgetFabric { // @override ViewAttribute? attributes; @@ -50,12 +50,10 @@ base class DuitElement extends TreeElement with WidgetFabric { ServerAction? serverAction; if (json["action"] != null) { - serverAction = ServerAction.fromJson(json["action"]); + serverAction = ServerAction.parse(json["action"]); - if (serverAction.executionType == 2) { - assert(serverAction.script != null, - "Script can't be null when executionType == 2"); - final script = serverAction.script!; + if (serverAction is ScriptAction) { + final script = serverAction.script; driver.evalScript(script.sourceCode); } } @@ -1290,7 +1288,7 @@ base class DuitElement extends TreeElement with WidgetFabric { static UIElementController? _createAndAttachController( String id, bool controlled, - ViewAttribute? attributes, + ViewAttribute attributes, ServerAction? action, UIDriver driver, String type, diff --git a/lib/src/ui/models/element_models.dart b/lib/src/ui/models/element_models.dart index aeb1769..60dbbe2 100644 --- a/lib/src/ui/models/element_models.dart +++ b/lib/src/ui/models/element_models.dart @@ -290,7 +290,7 @@ final class StackUIElement extends DuitElement } base class CustomUiElement extends DuitElement { - final Iterable subviews; + final Iterable subviews; CustomUiElement({ required super.id, diff --git a/lib/src/ui/models/ui_tree.dart b/lib/src/ui/models/ui_tree.dart index f527fbd..2fd3622 100644 --- a/lib/src/ui/models/ui_tree.dart +++ b/lib/src/ui/models/ui_tree.dart @@ -4,7 +4,7 @@ import 'package:flutter_duit/src/utils/index.dart'; import 'element.dart'; -final class DuitTree extends DuitAbstractTree { +final class DuitTree extends ElementTree { Widget? _root; final _dm = DevMetrics(); @@ -14,7 +14,7 @@ final class DuitTree extends DuitAbstractTree { }) : super(json: json, driver: driver); @override - Future parse() async { + Future parse() async { _dm.add(ParseModelStartMessage()); uiRoot = DuitElement.fromJson( json, @@ -25,7 +25,7 @@ final class DuitTree extends DuitAbstractTree { } @override - DuitAbstractTree parseSync() { + ElementTree parseSync() { _dm.add(ParseModelStartMessage()); uiRoot = DuitElement.fromJson( json, diff --git a/lib/src/ui/widgets/transform.dart b/lib/src/ui/widgets/transform.dart index 5d071b8..d56edba 100644 --- a/lib/src/ui/widgets/transform.dart +++ b/lib/src/ui/widgets/transform.dart @@ -5,7 +5,7 @@ import "package:flutter_duit/src/animations/index.dart"; import "package:flutter_duit/src/attributes/index.dart"; class DuitTransform extends StatelessWidget with AnimatedAttributes { - final ViewAttribute attributes; + final ViewAttribute attributes; final Widget child; const DuitTransform({ @@ -69,13 +69,15 @@ class DuitTransform extends StatelessWidget with AnimatedAttributes { ); } - return SizedBox.shrink(child: child); + return SizedBox.shrink( + child: child, + ); } } class DuitControlledTransform extends StatefulWidget with AnimatedAttributes { final Widget child; - final UIElementController controller; + final UIElementController controller; const DuitControlledTransform({ super.key, diff --git a/lib/src/ui/widgets/widget_fabric.dart b/lib/src/ui/widgets/widget_fabric.dart index 4c0eada..db84a3b 100644 --- a/lib/src/ui/widgets/widget_fabric.dart +++ b/lib/src/ui/widgets/widget_fabric.dart @@ -271,7 +271,7 @@ mixin WidgetFabric { child: child, ); case ElementType.transform: - final it = model as TransformUiElement; + final it = model as TransformUiElement; final child = getWidgetFromElement(it.child); return it.controlled diff --git a/lib/src/utils/detach_extension.dart b/lib/src/utils/detach_extension.dart deleted file mode 100644 index 4b961b6..0000000 --- a/lib/src/utils/detach_extension.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:duit_kernel/duit_kernel.dart'; - -extension type DetachableController(UIElementController controller) implements UIElementController { - void detach() { - controller.driver.detachController(controller.id); - } -} diff --git a/lib/src/utils/get_action.dart b/lib/src/utils/get_action.dart new file mode 100644 index 0000000..0e55007 --- /dev/null +++ b/lib/src/utils/get_action.dart @@ -0,0 +1,13 @@ +import 'package:duit_kernel/duit_kernel.dart'; + +extension type ActionCreator(Map json) { + ServerAction? getActionFromJson(String name) { + final hasProp = json.containsKey(name); + + if (!hasProp) { + return null; + } else { + return ServerAction.parse(json[name]); + } + } +} diff --git a/lib/src/utils/index.dart b/lib/src/utils/index.dart index ffc2422..54c5cc2 100644 --- a/lib/src/utils/index.dart +++ b/lib/src/utils/index.dart @@ -8,5 +8,4 @@ export 'gesture_interceptor.dart'; export 'json_utils.dart'; export 'parse_layout.dart'; export 'meta_data.dart'; -export 'dev/index.dart'; -export 'detach_extension.dart'; +export 'dev/index.dart'; \ No newline at end of file diff --git a/lib/src/utils/json_utils.dart b/lib/src/utils/json_utils.dart index 312dc49..ad04f6f 100644 --- a/lib/src/utils/json_utils.dart +++ b/lib/src/utils/json_utils.dart @@ -1,12 +1,10 @@ import 'package:duit_kernel/duit_kernel.dart'; -import 'jsono.dart'; - class JsonUtils { ///Modifies the original [model] object by adding values from the [dataSource] ///object to it if there are [ValueReference] objects in the attributes static Map mergeWithDataSource( - DuitComponentDescription model, + ComponentDescription model, Map dataSource, ) { for (var rwT in model.refs) { @@ -28,69 +26,4 @@ class JsonUtils { return model.data; } - - ///Modifies the original [layout] object by adding values from the [data] - ///object to it if there are [ValueReference] objects in the attributes - @Deprecated("Use mergeWithDataSource instead") - static Map fillComponentProperties( - JSONObject layout, - JSONObject dataSource, - ) { - final src = Map.from(layout); - - void replaceId(JSONObject map) { - if (map["controlled"] == true) { - final type = map["type"]; - final timestamp = DateTime.now().microsecondsSinceEpoch; - final id = "${type}_$timestamp"; - map["id"] = id; - } - } - - if (dataSource.values.isEmpty) { - replaceId(src); - return src; - } - - final attributes = src["attributes"]; - final refs = List.from(attributes?["refs"] ?? []); - - replaceId(src); - - for (var ref in refs) { - final payload = ValueReference.fromJson(ref); - final value = dataSource[payload.objectKey]; - - if (value != null) { - attributes?[payload.attributeKey] = value; - } - } - - if (src["child"] != null) { - final child = fillComponentProperties( - src["child"], - dataSource, - ); - - src["child"] = child; - } - - if (src["children"] != null) { - final children = List.from(src["children"]); - final ln = children.length; - final arr = []; - for (var i = 0; i < ln; i++) { - final child = fillComponentProperties( - children[i], - dataSource, - ); - - arr.add(child); - } - - src["children"] = arr; - } - - return src; - } } diff --git a/lib/src/utils/parse_layout.dart b/lib/src/utils/parse_layout.dart index e74bd80..9417b5a 100644 --- a/lib/src/utils/parse_layout.dart +++ b/lib/src/utils/parse_layout.dart @@ -3,7 +3,7 @@ import 'package:flutter_duit/src/ui/models/ui_tree.dart'; import 'index.dart'; -Future parseLayout( +Future parseLayout( JSONObject data, UIDriver driver, ) async { @@ -13,7 +13,7 @@ Future parseLayout( ).parse(); } -DuitAbstractTree parseLayoutSync( +ElementTree parseLayoutSync( JSONObject data, UIDriver driver, ) { diff --git a/pubspec.yaml b/pubspec.yaml index 323591b..955865f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ environment: flutter: ">=1.17.0" dependencies: - duit_kernel: ^2.1.3 + duit_kernel: ^3.0.0 flutter: sdk: flutter http: ^1.1.0