From 839395af8f216e237f90db884d9064a98bb31e14 Mon Sep 17 00:00:00 2001 From: ebwood Date: Fri, 15 Mar 2024 00:42:14 +0800 Subject: [PATCH] map widget to constructorcall --- melos.yaml | 1 + packages/widget2rfw/.gitignore | 29 + packages/widget2rfw/.metadata | 10 + packages/widget2rfw/CHANGELOG.md | 3 + packages/widget2rfw/LICENSE | 21 + packages/widget2rfw/README.md | 39 + packages/widget2rfw/analysis_options.yaml | 4 + .../lib/flutter/arguments_encoder.dart | 1302 +++++++++++++++++ .../widget2rfw/lib/flutter/core_widgets.dart | 848 +++++++++++ .../lib/flutter/material_widgets.dart | 524 +++++++ packages/widget2rfw/lib/flutter/runtime.dart | 112 ++ packages/widget2rfw/lib/widget2rfw.dart | 1 + packages/widget2rfw/pubspec.lock | 245 ++++ packages/widget2rfw/pubspec.yaml | 21 + 14 files changed, 3160 insertions(+) create mode 100644 packages/widget2rfw/.gitignore create mode 100644 packages/widget2rfw/.metadata create mode 100644 packages/widget2rfw/CHANGELOG.md create mode 100644 packages/widget2rfw/LICENSE create mode 100644 packages/widget2rfw/README.md create mode 100644 packages/widget2rfw/analysis_options.yaml create mode 100644 packages/widget2rfw/lib/flutter/arguments_encoder.dart create mode 100644 packages/widget2rfw/lib/flutter/core_widgets.dart create mode 100644 packages/widget2rfw/lib/flutter/material_widgets.dart create mode 100644 packages/widget2rfw/lib/flutter/runtime.dart create mode 100644 packages/widget2rfw/lib/widget2rfw.dart create mode 100644 packages/widget2rfw/pubspec.lock create mode 100644 packages/widget2rfw/pubspec.yaml diff --git a/melos.yaml b/melos.yaml index 64dde17..a884e84 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,6 +3,7 @@ name: rfwt packages: - packages/txt2rfw - packages/rfw2txt + - packages/widget2rfw scripts: analyze: diff --git a/packages/widget2rfw/.gitignore b/packages/widget2rfw/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/packages/widget2rfw/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/packages/widget2rfw/.metadata b/packages/widget2rfw/.metadata new file mode 100644 index 0000000..b10e487 --- /dev/null +++ b/packages/widget2rfw/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ba393198430278b6595976de84fe170f553cc728" + channel: "stable" + +project_type: package diff --git a/packages/widget2rfw/CHANGELOG.md b/packages/widget2rfw/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/packages/widget2rfw/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/widget2rfw/LICENSE b/packages/widget2rfw/LICENSE new file mode 100644 index 0000000..4a4ef7c --- /dev/null +++ b/packages/widget2rfw/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ebwood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/widget2rfw/README.md b/packages/widget2rfw/README.md new file mode 100644 index 0000000..02fe8ec --- /dev/null +++ b/packages/widget2rfw/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## 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. + +## 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. diff --git a/packages/widget2rfw/analysis_options.yaml b/packages/widget2rfw/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/widget2rfw/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/widget2rfw/lib/flutter/arguments_encoder.dart b/packages/widget2rfw/lib/flutter/arguments_encoder.dart new file mode 100644 index 0000000..78d1ddf --- /dev/null +++ b/packages/widget2rfw/lib/flutter/arguments_encoder.dart @@ -0,0 +1,1302 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// There's a lot of [] lists in this file so to avoid making this +// file even less readable we relax our usual stance on verbose typing. +// ignore_for_file: always_specify_types + +// This file is hand-formatted. + +// ignore: unnecessary_import, see https://github.com/flutter/flutter/pull/138881 + +import 'dart:collection'; + +import 'package:flutter/material.dart'; + +import 'package:rfw/rfw.dart'; + +import 'core_widgets.dart'; +import 'material_widgets.dart'; + +// ignore: unnecessary_import +import 'dart:ui' + show + FontFeature; // TODO(ianh): https://github.com/flutter/flutter/issues/87235 + + + +/// Signature for methods that decode structured values from a [DataSource], +/// such as the static methods of [ArgumentEncoders]. +/// +/// Used to make some of the methods of that class extensible. +typedef ArgumentEncoder = T Function(S source); + +/// A map that does not allow null values. +class NotNullMap extends MapBase { + final Map _map = {}; + + NotNullMap(); + + factory NotNullMap.from(Map map) { + final notNullMap = NotNullMap(); + map.forEach((key, value) => notNullMap[key] = value); + return notNullMap; + } + + @override + V? operator [](Object? key) => _map[key]; + + @override + void operator []=(K key, V value) { + if (value == null) return; + _map[key] = value; + } + + @override + void clear() => _map.clear(); + + @override + Iterable get keys => _map.keys; + + @override + V? remove(Object? key) => _map.remove(key); +} + +/// A visitor for [Widget]s. +abstract class WidgetVisitor { + ConstructorCall visit(Widget widget); +} + +/// A set of methods for decoding structured values from a [DataSource]. +/// +/// Specifically, these methods decode types that are used by local widgets +/// (q.v. [createCoreWidgets]). +/// +/// These methods take a [DataSource] and a `key`. The `key` is a path to the +/// part of the [DataSource] that the value should be read from. This may +/// identify a map, a list, or a leaf value, depending on the needs of the +/// method. +class ArgumentEncoders { + const ArgumentEncoders._(); + + /// This is a workaround for https://github.com/dart-lang/sdk/issues/47021 + // ignore: unused_field, constant_identifier_names + static const ArgumentEncoders __ = + ArgumentEncoders._(); // ignore: unused_field + + // (in alphabetical order) + + /// Decodes an [AlignmentDirectional] or [Alignment] object out of the + /// specified map. + /// + /// If the map has `start` and `y` keys, then it is interpreted as an + /// [AlignmentDirectional] with those values. Otherwise if it has `x` and `y` + /// it's an [Alignment] with those values. Otherwise it returns null. + static Map? alignment(AlignmentGeometry? alignment) { + if (alignment == null) { + return null; + } + if (alignment is Alignment) { + return { + 'x': alignment.x, + 'y': alignment.y, + }; + } else if (alignment is AlignmentDirectional) { + return { + 'start': alignment.start, + 'y': alignment.y, + }; + } + return {}; + } + + /// Decodes the specified map into a [BoxConstraints]. + /// + /// The keys used are `minWidth`, `maxWidth`, `minHeight`, and `maxHeight`. + /// Omitted keys are defaulted to 0.0 for minimums and infinity for maximums. + static Map? boxConstraints(BoxConstraints? constraints) { + if (constraints == null) return null; + return { + 'minWidth': constraints.minWidth, + 'maxWidth': constraints.maxWidth, + 'minHeight': constraints.minHeight, + 'maxHeight': constraints.maxHeight, + }; + } + + /// Returns a [BorderDirectional] from the specified list. + /// + /// The list is a list of values as interpreted by [borderSide]. An empty or + /// missing list results in a null return value. The list should have one + /// through four items. Extra items are ignored. + /// + /// The values are interpreted as follows: + /// + /// * start: first value. + /// * top: second value, defaulting to same as start. + /// * end: third value, defaulting to same as start. + /// * bottom: fourth value, defaulting to same as top. + static List?>? border(BoxBorder? border) { + if (border == null) return null; + + if (border is Border) { + return [ + borderSide(border.left), + borderSide(border.top), + borderSide(border.right), + borderSide(border.bottom), + ]; + } else if (border is BorderDirectional) { + return [ + borderSide(border.start), + borderSide(border.top), + borderSide(border.end), + borderSide(border.bottom), + ]; + } + // TODO: custom border? + return null; + } + + /// Returns a [BorderRadiusDirectional] from the specified list. + /// + /// The list is a list of values as interpreted by [radius]. An empty or + /// missing list results in a null return value. The list should have one + /// through four items. Extra items are ignored. + /// + /// The values are interpreted as follows: + /// + /// * topStart: first value. + /// * topEnd: second value, defaulting to same as topStart. + /// * bottomStart: third value, defaulting to same as topStart. + /// * bottomEnd: fourth value, defaulting to same as topEnd. + static List?>? borderRadius( + BorderRadiusGeometry? borderRadiusGeometry) { + if (borderRadiusGeometry == null) { + return null; + } + + final borderRadius = borderRadiusGeometry as BorderRadius; + return [ + radius(borderRadius.topLeft), + radius(borderRadius.topRight), + radius(borderRadius.bottomLeft), + radius(borderRadius.bottomRight), + ]; + } + + /// Returns a [BorderSide] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [BorderSide] is created from the + /// keys `color` (see [color], defaults to black), `width` (a double, defaults + /// to 1.0), and `style` (see [enumValue] for [BorderStyle], defaults to + /// [BorderStyle.solid]). + static Map? borderSide(BorderSide? borderSide) { + if (borderSide == null) return null; + return { + 'color': color(borderSide.color), + 'width': borderSide.width, + 'style': enumValue(borderSide.style), + }; + } + + /// Returns a [BoxShadow] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [BoxShadow] is created from the + /// keys `color` (see [color], defaults to black), `offset` (see [offset], + /// defaults to [Offset.zero]), `blurRadius` (double, defaults to zero), and + /// `spreadRadius` (double, defaults to zero). + static Map? boxShadow(Shadow? boxShadow) { + if (boxShadow == null) return null; + return { + 'color': color(boxShadow.color), + 'offset': offset(boxShadow.offset), + 'blurRadius': boxShadow.blurRadius, + if (boxShadow is BoxShadow) 'spreadRadius': boxShadow.spreadRadius, + }; + } + + /// Returns a [Color] from the specified integer. + /// + /// Returns null if it's not an integer; otherwise, passes it to the [ + /// Color] constructor. + static String? color(Color? color) { + if (color == null) { + return null; + } + return '0x${color.value.toRadixString(16).toUpperCase().toUpperCase()}'; + } + + /// Returns a [ColorFilter] from the specified map. + /// + /// The `type` key specifies the kind of filter. + /// + /// A type of `linearToSrgbGamma` creates a [ColorFilter.linearToSrgbGamma]. + /// + /// A type of `matrix` creates a [ColorFilter.matrix], parsing the `matrix` + /// key as per [colorMatrix]). If there is no `matrix` key, returns null. + /// + /// A type of `mode` creates a [ColorFilter.mode], using the `color` key + /// (see[color], defaults to black) and the `blendMode` key (see [enumValue] for + /// [BlendMdoe], defaults to [BlendMode.srcOver]) + /// + /// A type of `srgbToLinearGamma` creates a [ColorFilter.srgbToLinearGamma]. + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [colorFilterDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? colorFilter(ColorFilter? colorFilter) { + if (colorFilter == null) return null; + // colorFilter._type is private, so use toString to get type + String type = colorFilter.toString(); + if (type.startsWith('ColorFilter.linearToSrgbGamma')) { + return { + 'type': 'linearToSrgbGamma', + }; + } else if (type.startsWith('ColorFilter.srgbToLinearGamma')) { + return { + 'type': 'srgbToLinearGamma', + }; + } else if (type.startsWith('ColorFilter.matrix')) { + String matrix = + type.substring('ColorFilter.matrix('.length, type.length - 1); + List matrixList = + matrix.substring(1, matrix.length - 1).split(', '); + return NotNullMap.from({ + 'type': 'matrix', + 'matrix': colorMatrix(matrixList), + }); + } else if (type.startsWith('ColorFilter.mode')) { + List colorAndMode = type + .substring('ColorFilter.mode('.length, type.length - 1) + .split(', '); + Color colorValue = Color(int.parse(colorAndMode[0])); + BlendMode blendMode = BlendMode.values + .firstWhere((element) => element.name == colorAndMode[1]); + return { + 'type': 'mode', + 'color': color(colorValue), + 'blendMode': enumValue(blendMode), + }; + } else { + final encoder = colorFilterEncoders[colorFilter.runtimeType]; + if (encoder == null) return null; + return encoder(colorFilter); + } + } + + /// Extension mechanism for [colorFilter]. + static final Map?>> + colorFilterEncoders = + ?>>{}; + + /// Returns a list of 20 doubles from the specified list. + /// + /// If the specified key does not identify a list, returns null instead. + /// + /// If the list has fewer than 20 entries or if any of the entries are not + /// doubles, any entries that could not be obtained are replaced by zero. + /// + /// Used by [colorFilter] in the `matrix` mode. + static List? colorMatrix(List? list) { + if (list == null) return null; + return list.map((e) => double.tryParse(e)).toList(); + } + + /// Returns a [Color] from the specified integer. + /// + /// Returns black if it's not an integer; otherwise, passes it to the [ + /// Color] constructor. + /// + /// This is useful in situations where null is not acceptable, for example, + /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v] + /// directly. + + /// Returns a [Curve] from the specified string. + /// + /// The given key should specify a string. If that string matches one of the + /// names of static curves defined in the [Curves] class, then that curve is + /// returned. Otherwise, if the string was not null, and is present as a key + /// in the [curveDecoders] map, then the matching decoder from that map is + /// invoked. Otherwise, the default obtained from [AnimationDefaults.curveOf] + /// is used (which is why a [BuildContext] is required). + static dynamic curve(Curve? curve) { + if (curve == null) return null; + + switch (curve) { + case Curves.linear: + return 'linear'; + case Curves.decelerate: + return 'decelerate'; + case Curves.fastLinearToSlowEaseIn: + return 'fastLinearToSlowEaseIn'; + case Curves.ease: + return 'ease'; + case Curves.easeIn: + return 'easeIn'; + case Curves.easeInToLinear: + return 'easeInToLinear'; + case Curves.easeInSine: + return 'easeInSine'; + case Curves.easeInQuad: + return 'easeInQuad'; + case Curves.easeInCubic: + return 'easeInCubic'; + case Curves.easeInQuart: + return 'easeInQuart'; + case Curves.easeInQuint: + return 'easeInQuint'; + case Curves.easeInExpo: + return 'easeInExpo'; + case Curves.easeInCirc: + return 'easeInCirc'; + case Curves.easeInBack: + return 'easeInBack'; + case Curves.easeOut: + return 'easeOut'; + case Curves.linearToEaseOut: + return 'linearToEaseOut'; + case Curves.easeOutSine: + return 'easeOutSine'; + case Curves.easeOutQuad: + return 'easeOutQuad'; + case Curves.easeOutCubic: + return 'easeOutCubic'; + case Curves.easeOutQuart: + return 'easeOutQuart'; + case Curves.easeOutQuint: + return 'easeOutQuint'; + case Curves.easeOutExpo: + return 'easeOutExpo'; + case Curves.easeOutCirc: + return 'easeOutCirc'; + case Curves.easeOutBack: + return 'easeOutBack'; + case Curves.easeInOut: + return 'easeInOut'; + case Curves.easeInOutSine: + return 'easeInOutSine'; + case Curves.easeInOutQuad: + return 'easeInOutQuad'; + case Curves.easeInOutCubic: + return 'easeInOutCubic'; + case Curves.easeInOutCubicEmphasized: + return 'easeInOutCubicEmphasized'; + case Curves.easeInOutQuart: + return 'easeInOutQuart'; + case Curves.easeInOutQuint: + return 'easeInOutQuint'; + case Curves.easeInOutExpo: + return 'easeInOutExpo'; + case Curves.easeInOutCirc: + return 'easeInOutCirc'; + case Curves.easeInOutBack: + return 'easeInOutBack'; + case Curves.fastOutSlowIn: + return 'fastOutSlowIn'; + case Curves.slowMiddle: + return 'slowMiddle'; + case Curves.bounceIn: + return 'bounceIn'; + case Curves.bounceOut: + return 'bounceOut'; + case Curves.bounceInOut: + return 'bounceInOut'; + case Curves.elasticIn: + return 'elasticIn'; + case Curves.elasticOut: + return 'elasticOut'; + case Curves.elasticInOut: + return 'elasticInOut'; + default: + final encoder = curveEncoders[curve.runtimeType]; + if (encoder != null) { + return encoder(curve); + } + return null; + } + } + + /// Extension mechanism for [curve]. + /// + /// The decoders must not return null. + /// + /// The given key will specify a string, which is known to not match any of + /// the values in [Curves]. + static final Map?>> + curveEncoders = ?>>{}; + + /// Returns a [Decoration] from the specified map. + /// + /// The `type` key specifies the kind of decoration. + /// + /// A type of `box` creates a [BoxDecoration] using the keys `color` + /// ([color]), `image` ([decorationImage]), `border` ([border]), + /// `borderRadius` ([borderRadius]), `boxShadow` (a [list] of [boxShadow]), + /// `gradient` ([gradient]), `backgroundBlendMode` (an [enumValue] of [BlendMode]), + /// and `shape` (an [enumValue] of [BoxShape]), these keys each corresponding to + /// the properties of [BoxDecoration] with the same name. + /// + /// A type of `flutterLogo` creates a [FlutterLogoDecoration] using the keys + /// `color` ([color], corresponds to [FlutterLogoDecoration.textColor]), + /// `style` ([enumValue] of [FlutterLogoStyle], defaults to + /// [FlutterLogoStyle.markOnly]), and `margin` ([edgeInsets], always with a + /// left-to-right direction), the latter two keys corresponding to + /// the properties of [FlutterLogoDecoration] with the same name. + /// + /// A type of `shape` creates a [ShapeDecoration] using the keys `color` + /// ([color]), `image` ([decorationImage]), `gradient` ([gradient]), `shadows` + /// (a [list] of [boxShadow]), and `shape` ([shapeBorder]), these keys each + /// corresponding to the properties of [ShapeDecoration] with the same name. + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [decorationDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? decoration(Decoration? decoration) { + if (decoration == null) return null; + + if (decoration is BoxDecoration) { + return NotNullMap.from({ + 'type': 'box', + 'color': color(decoration.color), + 'image': decorationImage(decoration.image), + 'border': border(decoration.border), + 'borderRadius': borderRadius(decoration.borderRadius), + 'boxShadow': list?>( + decoration.boxShadow, boxShadow), + 'gradient': gradient(decoration.gradient), + 'backgroundBlendMode': + enumValue(decoration.backgroundBlendMode), + 'shape': enumValue(decoration.shape), + }); + } else if (decoration is FlutterLogoDecoration) { + return NotNullMap.from({ + 'type': 'flutterLogo', + 'color': color(decoration.textColor), + 'style': enumValue(decoration.style), + 'margin': edgeInsets(decoration.margin), + }); + } else if (decoration is ShapeDecoration) { + return NotNullMap.from({ + 'type': 'shape', + 'color': color(decoration.color), + 'image': decorationImage(decoration.image), + 'gradient': gradient(decoration.gradient), + 'shadows': list?>( + decoration.shadows, boxShadow), + 'shape': shapeBorder(decoration.shape), + }); + } + + final encoder = decorationEncoders[decoration.runtimeType]; + if (encoder != null) { + return encoder(decoration); + } + return null; + } + + /// Extension mechanism for [decoration]. + static final Map?>> + decorationEncoders = + ?>>{}; + + /// Returns a [DecorationImage] from the specified map. + /// + /// The [DecorationImage.image] is determined by interpreting the same key as + /// per [imageProvider]. If that method returns null, then this returns null + /// also. Otherwise, the return value is used as the provider and additional + /// keys map to the identically-named properties of [DecorationImage]: + /// `onError` (must be an event handler; the payload map is augmented by an + /// `exception` key that contains the text serialization of the exception and + /// a `stackTrace` key that contains the stack trace, also as a string), + /// `colorFilter` ([colorFilter]), `fit` ([enumValue] of [BoxFit]), `alignment` + /// ([alignment], defaults to [Alignment.center]), `centerSlice` ([rect]), + /// `repeat` ([enumValue] of [ImageRepeat], defaults to [ImageRepeat.noRepeat]), + /// `matchTextDirection` (boolean, defaults to false). + static Map? decorationImage(DecorationImage? image) { + if (image == null) return null; + final provider = imageProvider(image.image); + return NotNullMap.from({ + ...?provider, + // TODO: still not sure about this + // 'onError': DataSourceEncoder.voidHandler(image.onError), + 'colorFilter': colorFilter(image.colorFilter), + 'fit': enumValue(image.fit), + 'alignment': alignment(image.alignment), + 'centerSlice': rect(image.centerSlice), + 'repeat': enumValue(image.repeat), + 'matchTextDirection': image.matchTextDirection, + 'scale': image.scale, + 'opacity': image.opacity, + 'filterQuality': enumValue(image.filterQuality), + 'invertColors': image.invertColors, + 'isAntiAlias': image.isAntiAlias, + }); + } + + /// Returns a double from the specified double. + /// + /// Returns 0.0 if it's not a double. + /// + /// This is useful in situations where null is not acceptable, for example, + /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v] + /// directly. + + /// Returns a [Duration] from the specified integer. + /// + /// If it's not an integer, the default obtained from + /// [AnimationDefaults.durationOf] is used (which is why a [BuildContext] is + /// required). + static int? duration(Duration? duration) { + return duration?.inMilliseconds; + } + + /// Returns an [EdgeInsetsDirectional] from the specified list. + /// + /// The list is a list of doubles. An empty or missing list results in a null + /// return value. The list should have one through four items. Extra items are + /// ignored. + /// + /// The values are interpreted as follows: + /// + /// * start: first value. + /// * top: second value, defaulting to same as start. + /// * end: third value, defaulting to same as start. + /// * bottom: fourth value, defaulting to same as top. + static List? edgeInsets(EdgeInsetsGeometry? edgeInsets) { + if (edgeInsets == null) { + return null; + } + EdgeInsets insets = edgeInsets.resolve(null); + return [insets.left, insets.top, insets.right, insets.bottom]; + } + + /// Returns one of the values of the specified enum `T`, from the specified string. + /// + /// The string must match the name of the enum value, excluding the enum type + /// name (the part of its [toString] after the dot). + /// + /// The first argument must be the `values` list for that enum; this is the + /// list of values that is searched. + /// + /// For example, `enumValue(TileMode.values, source, ['tileMode']) ?? + /// TileMode.clamp` reads the `tileMode` key of `source`, and looks for the + /// first match in [TileMode.values], defaulting to [TileMode.clamp] if + /// nothing matches; thus, the string `mirror` would return [TileMode.mirror]. + static String? enumValue(T? key) { + return key.toString().split('.').last; + } + + /// Returns a [FontFeature] from the specified map. + /// + /// The `feature` key is used as the font feature name (defaulting to the + /// probably-useless private value "NONE"), and the `value` key is used as the + /// value (defaulting to 1, which typically means "enabled"). + /// + /// As this never returns null, it is possible to use it with [list]. + static Map? fontFeature(FontFeature? feature) { + if (feature == null) return null; + return NotNullMap.from( + {'feature': feature.feature, 'value': feature.value}); + } + + /// Returns a [Gradient] from the specified map. + /// + /// The `type` key specifies the kind of gradient. + /// + /// A type of `linear` creates a [LinearGradient] using the keys `begin` + /// ([alignment], defaults to [Alignment.centerLeft]), `end` ([alignment], + /// defaults to [Alignment.centerRight]), `colors` ([list] of [colorOrBlack], + /// defaults to a two-element list with black and white), `stops` ([list] of + /// [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode], defaults to + /// [TileMode.clamp]), these keys each corresponding to the properties of + /// [BoxDecoration] with the same name. + /// + /// A type of `radial` creates a [RadialGradient] using the keys `center` + /// ([alignment], defaults to [Alignment.center]), `radius' (double, defaults + /// to 0.5), `colors` ([list] of [colorOrBlack], defaults to a two-element + /// list with black and white), `stops` ([list] of [doubleOrZero]), `tileMode` + /// ([enumValue] of [TileMode], defaults to [TileMode.clamp]), `focal` + /// (([alignment]), and `focalRadius` (double, defaults to zero), these keys + /// each corresponding to the properties of [BoxDecoration] with the same + /// name. + /// + /// A type of `linear` creates a [LinearGradient] using the keys `center` + /// ([alignment], defaults to [Alignment.center]), `startAngle` (double, + /// defaults to 0.0), `endAngle` (double, defaults to 2π), `colors` ([list] of + /// [colorOrBlack], defaults to a two-element list with black and white), + /// `stops` ([list] of [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode], + /// defaults to [TileMode.clamp]), these keys each corresponding to the + /// properties of [BoxDecoration] with the same name. + /// + /// The `transform` property of these gradient classes is not supported. + // TODO(ianh): https://github.com/flutter/flutter/issues/87208 + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [gradientDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? gradient(Gradient? gradient) { + if (gradient == null) return null; + + switch (gradient) { + case LinearGradient(): + return NotNullMap.from({ + 'type': 'linear', + 'begin': alignment(gradient.begin), + 'end': alignment(gradient.end), + 'colors': list(gradient.colors, color), + 'stops': list(gradient.stops), + 'tileMode': enumValue(gradient.tileMode) + }); + case RadialGradient(): + return NotNullMap.from({ + 'type': 'radial', + 'center': alignment(gradient.center), + 'colors': list(gradient.colors, color), + 'stops': list(gradient.stops), + 'tileMode': enumValue(gradient.tileMode), + 'focal': alignment(gradient.focal), + 'focalRadius': gradient.focalRadius + }); + case SweepGradient(): + return NotNullMap.from({ + 'type': 'sweep', + 'center': alignment(gradient.center), + 'startAngle': gradient.startAngle, + 'endAngle': gradient.endAngle, + 'colors': list(gradient.colors, color), + 'stops': list(gradient.stops), + 'tileMode': enumValue(gradient.tileMode), + }); + default: + final encoder = gradientEncoders[gradient.runtimeType]; + if (encoder == null) return null; + return encoder(gradient); + } + } + + /// Extension mechanism for [gradient]. + static final Map?>> + gradientEncoders = + ?>>{}; + + /// Returns a [SliverGridDelegate] from the specified map. + /// + /// The `type` key specifies the kind of grid delegate. + /// + /// A type of `fixedCrossAxisCount` creates a + /// [SliverGridDelegateWithFixedCrossAxisCount] using the keys + /// `crossAxisCount`, `mainAxisSpacing`, `crossAxisSpacing`, + /// `childAspectRatio`, and `mainAxisExtent`. + /// + /// A type of `maxCrossAxisExtent` creates a + /// [SliverGridDelegateWithMaxCrossAxisExtent] using the keys + /// maxCrossAxisExtent:`, `mainAxisSpacing`, `crossAxisSpacing`, + /// `childAspectRatio`, and `mainAxisExtent`. + /// + /// The types (int or double) and defaults for these keys match the + /// identically named arguments to the default constructors of those classes. + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [gridDelegateDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? gridDelegate(SliverGridDelegate? delegate) { + if (delegate == null) return null; + if (delegate is SliverGridDelegateWithFixedCrossAxisCount) { + return NotNullMap.from({ + 'type': 'fixedCrossAxisCount', + 'crossAxisCount': delegate.crossAxisCount, + 'mainAxisSpacing': delegate.mainAxisSpacing, + 'crossAxisSpacing': delegate.crossAxisSpacing, + 'childAspectRatio': delegate.childAspectRatio, + 'mainAxisExtent': delegate.mainAxisExtent + }); + } else if (delegate is SliverGridDelegateWithMaxCrossAxisExtent) { + return NotNullMap.from({ + 'type': 'maxCrossAxisExtent', + 'maxCrossAxisExtent': delegate.maxCrossAxisExtent, + 'mainAxisSpacing': delegate.mainAxisSpacing, + 'crossAxisSpacing': delegate.crossAxisSpacing, + 'childAspectRatio': delegate.childAspectRatio, + 'mainAxisExtent': delegate.mainAxisExtent + }); + } + + final encoder = gridDelegateEncoders[delegate.runtimeType]; + if (encoder == null) return null; + return encoder(delegate); + } + + /// TODO wombat: All this custom encoders should have a type for every specific class provided to user, let them provide the implementation + /// + /// Extension mechanism for [gridDelegate]. + static final Map?>> + gridDelegateEncoders = + ?>>{}; + + /// Returns an [IconData] from the specified map. + /// + /// If the map does not have an `icon` key that is an integer, returns null. + /// + /// Otherwise, returns an [IconData] with the [IconData.codePoint] set to the + /// integer from the `icon` key, the [IconData.fontFamily] set to the string + /// from the `fontFamily` key, and the [IconData.matchTextDirection] set to + /// the boolean from the `matchTextDirection` key (defaulting to false). + /// + /// For Material Design icons (those from the [Icons] class), the code point + /// can be obtained from the documentation for the icon, and the font family + /// is `MaterialIcons`. For example, [Icons.chalet] would correspond to + /// `{ icon: 0xe14f, fontFamily: 'MaterialIcons' }`. + /// + /// When building the release build of an application that uses the RFW + /// package, because this method creates non-const [IconData] objects + /// dynamically, the `--no-tree-shake-icons` option must be used. + static Map? iconData(IconData? icon) { + if (icon == null) return null; + return NotNullMap.from({ + 'icon': icon.codePoint, + 'fontFamily': icon.fontFamily, + 'matchTextDirection': icon.matchTextDirection + }); + } + + /// Returns an [IconThemeData] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [IconThemeData] is created from + /// the following keys: 'color` ([color]), `opacity` (double), `size` + /// (double). + static Map? iconThemeData(IconThemeData? data) { + if (data == null) return null; + return NotNullMap.from({ + 'color': data.color, + 'opacity': data.opacity, + 'size': data.size, + }); + } + + /// Returns an [ImageProvider] from the specifed map. + /// + /// The `source` key of the specified map is controlling. It must be a string. + /// If its value is one of the keys in [imageProviderDecoders], then the + /// relevant decoder is invoked and its return value is used (even if it is + /// null). + /// + /// Otherwise, if the `source` key gives an absolute URL (one with a scheme), + /// then a [NetworkImage] with that URL is returned. Its scale is given by the + /// `scale` key (double, defaults to 1.0). + /// + /// Otherwise, if the `source` key gives a relative URL (i.e. it can be parsed + /// as a URL and has no scheme), an [AssetImage] with the name given by the + /// `source` key is returned. + /// + /// Otherwise, if there is no `source` key in the map, or if that cannot be + /// parsed as a URL (absolute or relative), null is returned. + static Map? imageProvider(ImageProvider? provider) { + if (provider == null) { + return null; + } + if (provider is ResizeImage) { + provider = provider.imageProvider; + } + + if (provider is NetworkImage) { + return {'source': provider.url, 'scale': provider.scale}; + } else if (provider is AssetImage) { + return {'source': provider.assetName}; + } + + final encoder = imageProviderEncoders[provider.runtimeType]; + if (encoder != null) { + return encoder(provider); + } + return null; + } + + /// Extension mechanism for [imageProvider]. + static final Map?>> + imageProviderEncoders = + ?>>{}; + + /// Returns a [List] of `T` values from the specified list, using the given + /// `decoder` to parse each value. + /// + /// If the list is absent _or empty_, returns null (not an empty list). + /// + /// Otherwise, returns a list with as many items as the specified list, with + /// each entry in the list decoded using `decoder`. + /// + /// If `T` is non-nullable, the decoder must also be non-nullable. + static List? list(List? source, + [ArgumentEncoder? encoder]) { + if (source == null || source.isEmpty) { + return null; + } + + return source + .map((e) => encoder == null ? (e as T) : encoder.call(e)) + .toList(); + } + + /// Returns a [Locale] from the specified string. + /// + /// The string is split on hyphens ("-"). + /// + /// If the string is null, returns null. + /// + /// If there is no hyphen in the list, uses the one-argument form of [ + /// Locale], passing the whole string. + /// + /// If there is one hyphen in the list, uses the two-argument form of [ + /// Locale], passing the parts before and after the hyphen respectively. + /// + /// If there are two or more hyphens, uses the [Locale.fromSubtags] + /// constructor. + static String? locale(Locale? locale) { + if (locale == null) return null; + List list = [ + locale.languageCode, + locale.scriptCode, + locale.countryCode + ]..removeWhere((element) => element != null); + return list.join('-'); + } + + /// Returns a list of 16 doubles from the specified list. + /// + /// If the list is missing or has fewer than 16 entries, returns null. + /// + /// Otherwise, returns a list of 16 entries, corresponding to the first 16 + /// entries of the specified list, with any non-double values replaced by 0.0. + static List? matrix(Matrix4? matrix) { + if (matrix == null) return null; + + return matrix.storage; + } + + /// Returns a [MaskFilter] from the specified map. + /// + /// The `type` key specifies the kind of mask filter. + /// + /// A type of `blur` creates a [MaskFilter.blur]. The `style` key ([enumValue] of + /// [BlurStyle], defaults to [BlurStyle.normal]) is used as the blur style, + /// and the `sigma` key (double, defaults to 1.0) is used as the blur sigma. + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [maskFilterDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? maskFilter(MaskFilter? filter) { + if (filter == null) return null; + String type = filter.toString(); + if (type.startsWith('MaskFilter.blur(')) { + List parts = type.split(', '); + return { + 'type': 'blur', + 'style': parts[0], + 'sigma': parts[1], + }; + } else { + final encoder = maskFilterEncoders[filter.runtimeType]; + if (encoder != null) { + return encoder(filter); + } + return null; + } + } + + /// Extension mechanism for [maskFilter]. + static final Map>> + maskFilterEncoders = + >>{}; + + /// Returns an [Offset] from the specified map. + /// + /// The map must have an `x` key and a `y` key, doubles. + static Map? offset(Offset? offset) { + if (offset == null) return null; + return {'x': offset.dx, 'y': offset.dy}; + } + + /// Returns a [Paint] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), a new [Paint] is created and its + /// properties are set according to the identically-named properties of the + /// map, as follows: + /// + /// * `blendMode`: [enumValue] of [BlendMode]. + /// * `color`: [color]. + /// * `colorFilter`: [colorFilter]. + /// * `filterQuality`: [enumValue] of [FilterQuality]. + // * `imageFilter`: [imageFilter]. + // * `invertColors`: boolean. + /// * `isAntiAlias`: boolean. + /// * `maskFilter`: [maskFilter]. + /// * `shader`: [shader]. + // * `strokeCap`: [enumValue] of [StrokeCap]. + // * `strokeJoin`: [enumValue] of [StrokeJoin]. + // * `strokeMiterLimit`: double. + // * `strokeWidth`: double. + // * `style`: [enumValue] of [PaintingStyle]. + /// + /// (Some values are not supported, because there is no way for them to be + /// used currently in RFW contexts.) + static Map? paint(Paint? paint) { + if (paint == null) return null; + return NotNullMap.from({ + 'blendMode': enumValue(paint.blendMode), + 'color': color(paint.color), + 'colorFilter': colorFilter(paint.colorFilter), + 'filterQuality': enumValue(paint.filterQuality), + // TODO: rfw not support imageFilter + // 'imageFilter': imageFilter(paint.imageFilter), + 'invertColors': paint.invertColors, + 'isAntiAlias': paint.isAntiAlias, + 'maskFilter': maskFilter(paint.maskFilter), + 'shader': shader(paint.shader), + 'strokeCap': enumValue(paint.strokeCap), + 'strokeJoin': enumValue(paint.strokeJoin), + 'strokeMiterLimit': paint.strokeMiterLimit, + 'strokeWidth': paint.strokeWidth, + 'style': enumValue(paint.style), + }); + } + + /// Returns a [Radius] from the specified map. + /// + /// The map must have an `x` value corresponding to [Radius.x], and may have a + /// `y` value corresponding to [Radius.y]. + /// + /// If the map only has an `x` key, the `y` value is assumed to be the same + /// (as in [Radius.circular]). + /// + /// If the `x` key is absent, the returned value is null. + static Map? radius(Radius? radius) { + if (radius == null) return null; + return { + 'x': radius.x, + 'y': radius.y, + }; + } + + /// Returns a [Rect] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise, returns a [Rect.fromLTWH] whose x, y, width, and height + /// components are determined from the `x`, `y`, `w`, and `h` properties of + /// the map, with missing (or non-double) values replaced by zeros. + static Map? rect(Rect? rect) { + if (rect == null) return null; + return {'x': rect.left, 'y': rect.top, 'w': rect.width, 'h': rect.height}; + } + + /// Returns a [ShapeBorder] from the specified map or list. + /// + /// If the key identifies a list, then each entry in the list is decoded by + /// recursively invoking [shapeBorder], and the result is the combination of + /// those [ShapeBorder] values as obtained using the [ShapeBorder.+] operator. + /// + /// Otherwise, if the key identifies a map with a `type` value, the map is + /// interpreted according to the `type` as follows: + /// + /// * `box`: the map's `sides` key is interpreted as a list by [border] and + /// the resulting [BoxBorder] (actually, [BorderDirectional]) is returned. + /// + /// * `beveled`: a [BeveledRectangleBorder] is returned; the `side` key is + /// interpreted by [borderSide] to set the [BeveledRectangleBorder.side] + /// (default of [BorderSide.none)), and the `borderRadius` key is + /// interpreted by [borderRadius] to set the + /// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]). + /// + /// * `circle`: a [CircleBorder] is returned; the `side` key is interpreted + /// by [borderSide] to set the [BeveledRectangleBorder.side] (default of + /// [BorderSide.none)). + /// + /// * `continuous`: a [ContinuousRectangleBorder] is returned; the `side` key + /// is interpreted by [borderSide] to set the [BeveledRectangleBorder.side] + /// (default of [BorderSide.none)), and the `borderRadius` key is + /// interpreted by [borderRadius] to set the + /// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]). + /// + /// * `rounded`: a [RoundedRectangleBorder] is returned; the `side` key is + /// interpreted by [borderSide] to set the [BeveledRectangleBorder.side] + /// (default of [BorderSide.none)), and the `borderRadius` key is + /// interpreted by [borderRadius] to set the + /// [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]). + /// + /// * `stadium`: a [StadiumBorder] is returned; the `side` key is interpreted + /// by [borderSide] to set the [BeveledRectangleBorder.side] (default of + /// [BorderSide.none)). + /// + /// If the type is none of these, then the type is looked up in + /// [shapeBorderDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, if type is null or is not found in [shapeBorderDecoders], returns null. + static Map? shapeBorder(ShapeBorder? shapeBorder) { + if (shapeBorder == null) return null; + + switch (shapeBorder) { + case Border(): + return NotNullMap.from({ + 'type': 'box', + 'sides': border(shapeBorder), + }); + case BeveledRectangleBorder(): + return NotNullMap.from({ + 'type': 'beveled', + 'side': borderSide(shapeBorder.side), + 'borderRadius': borderRadius(shapeBorder.borderRadius), + }); + case CircleBorder(): + return NotNullMap.from( + {'type': 'circle', 'side': borderSide(shapeBorder.side)}); + case ContinuousRectangleBorder(): + return NotNullMap.from({ + 'type': 'continuous', + 'size': borderSide(shapeBorder.side), + 'borderRadius': borderRadius(shapeBorder.borderRadius) + }); + case StadiumBorder(): + return NotNullMap.from( + {'type': 'stadium', 'side': borderSide(shapeBorder.side)}); + default: + final encoder = shapeBorderEncoders[shapeBorder.runtimeType]; + if (encoder == null) return null; + return encoder(shapeBorder); + } + } + + /// Extension mechanism for [shapeBorder]. + static final Map?>> + shapeBorderEncoders = + ?>>{}; + + /// Returns a [Shader] based on the specified map. + /// + /// The `type` key specifies the kind of shader. + /// + /// A type of `linear`, `radial`, or `sweep` is interpreted as described by + /// [gradient]; then, the gradient is compiled to a shader by applying the + /// `rect` (interpreted by [rect]) and `textDirection` (interpreted as an + /// [enumValue] of [TextDirection]) using the [Gradient.createShader] method. + /// + /// If the type is none of these, but is not null, then the type is looked up + /// in [shaderDecoders], and if an entry is found, this method defers to + /// that callback. + /// + /// Otherwise, returns null. + static Map? shader(Shader? shader) { + if (shader == null) return null; + // TODO: not implement + throw UnimplementedError(); + } + + /// Extension mechanism for [shader]. + static final Map?>> + shaderEncoders = >>{}; + + /// Returns a string from the specified string. + /// + /// Returns the empty string if it's not a string. + /// + /// This is useful in situations where null is not acceptable, for example, + /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v] + /// directly. + static String? string(String? value) { + if (value == null) return null; + return '\'$value\''; + } + + /// Returns a [StrutStyle] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [StrutStyle] is created from the + /// following keys: 'fontFamily` (string), `fontFamilyFallback` ([list] of + /// [string]), `fontSize` (double), `height` (double), `leadingDistribution` + /// ([enumValue] of [TextLeadingDistribution]), `leading` (double), + /// `fontWeight` ([enumValue] of [FontWeight]), `fontStyle` ([enumValue] of + /// [FontStyle]), `forceStrutHeight` (boolean). + static Map? strutStyle(StrutStyle? style) { + if (style == null) return null; + return NotNullMap.from({ + 'fontFamily': style.fontFamily, + 'fontFamilyFallback': + list(style.fontFamilyFallback, string), + 'fontSize': style.fontSize, + 'height': style.height, + 'leadingDistribution': enumValue(style.leadingDistribution), + 'leading': style.leading, + 'fontWeight': enumValue(style.fontWeight), + 'fontStyle': enumValue(style.fontStyle), + 'forceStrutHeight': style.forceStrutHeight + }); + } + + /// Returns a [TextHeightBehavior] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [TextHeightBehavior] is created + /// from the following keys: 'applyHeightToFirstAscent` (boolean; defaults to + /// true), `applyHeightToLastDescent` (boolean, defaults to true), and + /// `leadingDistribution` ([enumValue] of [TextLeadingDistribution], deafults + /// to [TextLeadingDistribution.proportional]). + static Map? textHeightBehavior( + TextHeightBehavior? behavior) { + if (behavior == null) return null; + return NotNullMap.from({ + 'applyHeightToFirstAscent': behavior.applyHeightToFirstAscent, + 'applyHeightToLastDescent': behavior.applyHeightToLastDescent, + 'leadingDistribution': enumValue(behavior.leadingDistribution), + }); + } + + /// Returns a [TextDecoration] from the specified list or string. + /// + /// If the key identifies a list, then each entry in the list is decoded by + /// recursively invoking [textDecoration], and the result is the combination + /// of those [TextDecoration] values as obtained using + /// [TextDecoration.combine]. + /// + /// Otherwise, if the key identifies a string, then the value `lineThrough` is + /// mapped to [TextDecoration.lineThrough], `overline` to + /// [TextDecoration.overline], and `underline` to [TextDecoration.underline]. + /// Other values (and the abscence of a value) are interpreted as + /// [TextDecoration.none]. + /// + /// @return null|String|List + static dynamic textDecoration(TextDecoration? decoration) { + if (decoration == null) return null; + + String type = decoration.toString(); + if (type.startsWith('TextDecoration.combine')) { + List combine = type + .substring('TextDecoration.combine('.length, type.length - 1) + .split(', '); + if (combine.isEmpty) return null; + if (combine.length == 1) return [combine.first]; + return combine; + } else { + switch (decoration) { + case TextDecoration.lineThrough: + return 'lineThrough'; + case TextDecoration.overline: + return 'overline'; + case TextDecoration.underline: + return 'underline'; + default: + return null; + } + } + } + + /// Returns a [TextStyle] from the specified map. + /// + /// If the map is absent, returns null. + /// + /// Otherwise (even if it has no keys), the [TextStyle] is created from the + /// following keys: `color` ([color]), `backgroundColor` ([color]), `fontSize` + /// (double), `fontWeight` ([enumValue] of [FontWeight]), `fontStyle` + /// ([enumValue] of [FontStyle]), `letterSpacing` (double), `wordSpacing` + /// (double), `textBaseline` ([enumValue] of [TextBaseline]), `height` + /// (double), `leadingDistribution` ([enumValue] of + /// [TextLeadingDistribution]), `locale` ([locale]), `foreground` ([paint]), + /// `background` ([paint]), `shadows` ([list] of [boxShadow]s), `fontFeatures` + /// ([list] of [fontFeature]s), `decoration` ([textDecoration]), + /// `decorationColor` ([color]), `decorationStyle` ([enumValue] of + /// [TextDecorationStyle]), `decorationThickness` (double), 'fontFamily` + /// (string), `fontFamilyFallback` ([list] of [string]), and `overflow` + /// ([enumValue] of [TextOverflow]). + static Map? textStyle(TextStyle? style) { + if (style == null) return null; + return NotNullMap.from({ + 'color': color(style.color), + 'backgroundColor': color(style.backgroundColor), + 'fontSize': style.fontSize, + 'fontWeight': enumValue(style.fontWeight), + 'fontStyle': enumValue(style.fontStyle), + 'letterSpacing': style.letterSpacing, + 'wordSpacing': style.wordSpacing, + 'textBaseline': enumValue(style.textBaseline), + 'height': style.height, + 'leadingDistribution': enumValue(style.leadingDistribution), + 'locale': locale(style.locale), + 'foreground': paint(style.foreground), + 'background': paint(style.background), + 'shadows': list?>(style.shadows, boxShadow), + 'fontFeatures': list?>( + style.fontFeatures, fontFeature), + 'decoration': textDecoration(style.decoration), + 'decorationColor': color(style.decorationColor), + 'decorationStyle': enumValue(style.decorationStyle), + 'decorationThickness': style.decorationThickness, + 'fontFamily': style.fontFamily, + 'fontFamilyFallback': + list(style.fontFamilyFallback, string), + }); + } + + /// Returns a [VisualDensity] from the specified string or map. + /// + /// If the specified value is a string, then it is interpreted as follows: + /// + /// * `adaptivePlatformDensity`: returns + /// [VisualDensity.adaptivePlatformDensity] (which varies by platform). + /// * `comfortable`: returns [VisualDensity.comfortable]. + /// * `compact`: returns [VisualDensity.compact]. + /// * `standard`: returns [VisualDensity.standard]. + /// + /// Otherwise, if the specified value is a map, then the keys `horizontal` and + /// `vertical` (doubles) are used to create a custom [VisualDensity]. The + /// specified values must be in the range given by + /// [VisualDensity.minimumDensity] to [VisualDensity.maximumDensity]. Missing + /// values are interpreted as zero. + static Map? visualDensity(VisualDensity? density) { + if (density == null) return null; + return {'horizontal': density.horizontal, 'vertical': density.vertical}; + } + + static ConstructorCall? widget(Widget? widget) { + if (widget == null) return null; + Map visitorMap = Map.from(coreWidgetsVisitorMap) + ..addAll(materialVisitorMap); + if (visitorMap.containsKey(widget.runtimeType)) { + return visitorMap[widget.runtimeType]!.visit(widget); + } + throw 'Unsupported widget type: ${widget.runtimeType}'; + } + + static List? widgetList(List? widgets) { + if (widgets == null) return null; + Map visitorMap = Map.from(coreWidgetsVisitorMap) + ..addAll(materialVisitorMap); + + return widgets.map((widget) { + if (visitorMap.containsKey(widget.runtimeType)) { + return visitorMap[widget.runtimeType]!.visit(widget); + } + throw 'Unsupported widget type: ${widget.runtimeType}'; + }).toList(); + } +} diff --git a/packages/widget2rfw/lib/flutter/core_widgets.dart b/packages/widget2rfw/lib/flutter/core_widgets.dart new file mode 100644 index 0000000..91cc7d0 --- /dev/null +++ b/packages/widget2rfw/lib/flutter/core_widgets.dart @@ -0,0 +1,848 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:rfw/rfw.dart'; + +import 'arguments_encoder.dart'; +import 'runtime.dart'; + +Map coreWidgetsVisitorMap = { + AnimatedAlign: AnimatedAlignVisitor(), + AnimatedContainer: AnimatedContainerVisitor(), + AnimatedDefaultTextStyle: AnimatedDefaultTextStyleVisitor(), + AnimatedOpacity: AnimatedOpacityVisitor(), + AnimatedPadding: AnimatedPaddingVisitor(), + AnimatedPositioned: AnimatedPositionedVisitor(), + AnimatedRotation: RotationVisitor(), + AnimatedScale: ScaleVisitor(), + AnimationDefaults: AnimationDefaultsVisitor(), + Align: AlignVisitor(), + AspectRatio: AspectRatioVisitor(), + Center: CenterVisitor(), + ClipRRect: ClipRRectVisitor(), + ColoredBox: ColoredBoxVisitor(), + Column: ColumnVisitor(), + Container: ContainerVisitor(), + DefaultTextStyle: DefaultTextStyleVisitor(), + Directionality: DirectionalityVisitor(), + Expanded: ExpandedVisitor(), + FittedBox: FittedBoxVisitor(), + FractionallySizedBox: FractionallySizedBoxVisitor(), + GestureDetector: GestureDetectorVisitor(), + GridView: GridViewVisitor(), + Icon: IconVisitor(), + IconTheme: IconThemeVisitor(), + IntrinsicHeight: IntrinsicHeightVisitor(), + IntrinsicWidth: IntrinsicWidthVisitor(), + Image: ImageVisitor(), + ListBody: ListBodyVisitor(), + ListView: ListViewVisitor(), + Opacity: OpacityVisitor(), + Padding: PaddingVisitor(), + Placeholder: PlaceholderVisitor(), + Positioned: PositionedVisitor(), + Row: RowVisitor(), + SafeArea: SafeAreaVisitor(), + SingleChildScrollView: SingleChildScrollViewVisitor(), + SizedBox: SizedBoxVisitor(), + Spacer: SpacerVisitor(), + Stack: StackVisitor(), + Text: TextVisitor(), + Wrap: WrapVisitor(), +}; + +sealed class CoreWidgetVisitor extends WidgetVisitor { + @override + ConstructorCall visit(covariant Widget widget); +} + +class AnimatedAlignVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedAlign widget) { + Map props = NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'widthFactor': widget.widthFactor, + 'heightFactor': widget.heightFactor, + 'child': ArgumentEncoders.widget(widget.child), + }); + return ConstructorCall('Align', props); + } +} + +class AnimatedContainerVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedContainer widget) { + Map? constraints = + ArgumentEncoders.boxConstraints(widget.constraints); + + NotNullMap props = NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + // TODO: if decoration is null, color will be added to new generated decoration, cannot get color from AnimatedContainer, need to be fixed. It's the other reason to use code_builder get props + // writeIfNotNull(widget.color, (value) { }) + // 'color': ; + 'decoration': ArgumentEncoders.decoration(widget.decoration), + 'foregroundDecoration': + ArgumentEncoders.decoration(widget.foregroundDecoration), + 'width': constraints?['width'], + 'height': constraints?['height'], + 'constraints': ArgumentEncoders.boxConstraints(widget.constraints), + 'margin': ArgumentEncoders.edgeInsets(widget.margin), + 'transform': ArgumentEncoders.matrix(widget.transform), + 'transformAlignment': + ArgumentEncoders.alignment(widget.transformAlignment), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'onEnd': DataSourceEncoder.voidHandler(widget.onEnd), + 'child': ArgumentEncoders.widget(widget.child), + }); + + return ConstructorCall('Container', props); + } +} + +class AnimatedDefaultTextStyleVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(covariant AnimatedDefaultTextStyle widget) { + return ConstructorCall( + 'DefaultTextStyle', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'style': ArgumentEncoders.textStyle(widget.style), + 'textAlign': ArgumentEncoders.enumValue(widget.textAlign), + 'softWrap': widget.softWrap, + 'overflow': ArgumentEncoders.enumValue(widget.overflow), + 'maxLines': widget.maxLines, + 'textWidthBasis': + ArgumentEncoders.enumValue(widget.textWidthBasis), + 'textHeightBehavior': + ArgumentEncoders.textHeightBehavior(widget.textHeightBehavior), + 'onEnd': DataSourceEncoder.voidHandler(widget.onEnd), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class AnimatedOpacityVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedOpacity widget) { + return ConstructorCall( + 'Opacity', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'onEnd': DataSourceEncoder.voidHandler(widget.onEnd), + 'opacity': widget.opacity, + 'alwaysIncludeSemantics': widget.alwaysIncludeSemantics, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class AnimatedPaddingVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedPadding widget) { + return ConstructorCall( + 'Padding', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'onEnd': DataSourceEncoder.voidHandler(widget.onEnd), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class AnimatedPositionedVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedPositioned widget) { + return ConstructorCall( + 'Positioned', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'start': widget.left, + 'top': widget.top, + 'end': widget.right, + 'bottom': widget.bottom, + 'width': widget.width, + 'height': widget.height, + 'onEnd': DataSourceEncoder.voidHandler(widget.onEnd), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class AnimationDefaultsVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimationDefaults widget) { + Map props = NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'child': ArgumentEncoders.widget(widget), + }); + + return ConstructorCall('${widget.runtimeType}', props); + } +} + +class AlignVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Align widget) { + Map props = NotNullMap.from({ + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'widthFactor': widget.widthFactor, + 'heightFactor': widget.heightFactor, + 'child': ArgumentEncoders.widget(widget.child), + }); + return ConstructorCall('Align', props); + } +} + +class AspectRatioVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AspectRatio widget) { + return ConstructorCall( + 'AspectRatio', + NotNullMap.from({ + 'aspectRatio': widget.aspectRatio, + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class CenterVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Center widget) { + return ConstructorCall( + 'Center', + NotNullMap.from({ + 'widthFactor': widget.widthFactor, + 'heightFactor': widget.heightFactor, + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class ClipRRectVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(ClipRRect widget) { + return ConstructorCall( + 'ClipRRect', + NotNullMap.from({ + 'borderRadius': ArgumentEncoders.borderRadius(widget.borderRadius), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class ColoredBoxVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(ColoredBox widget) { + return ConstructorCall( + 'ColoredBox', + NotNullMap.from({ + 'color': ArgumentEncoders.color(widget.color), + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class ColumnVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Column widget) { + return ConstructorCall( + 'Column', + NotNullMap.from({ + 'mainAxisAlignment': ArgumentEncoders.enumValue( + widget.mainAxisAlignment), + 'mainAxisSize': + ArgumentEncoders.enumValue(widget.mainAxisSize), + 'crossAxisAlignment': ArgumentEncoders.enumValue( + widget.crossAxisAlignment), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'verticalDirection': ArgumentEncoders.enumValue( + widget.verticalDirection), + 'textBaseline': + ArgumentEncoders.enumValue(widget.textBaseline), + 'children': ArgumentEncoders.widgetList(widget.children), + }), + ); + } +} + +class ContainerVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Container widget) { + // TODO: the value is already set to constraints, this another reason to get width/height from code generator tool + Map? constraints = + ArgumentEncoders.boxConstraints(widget.constraints); + + return ConstructorCall( + 'Container', + NotNullMap.from({ + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'color': ArgumentEncoders.color(widget.color), + 'decoration': ArgumentEncoders.decoration(widget.decoration), + 'foregroundDecoration': + ArgumentEncoders.decoration(widget.foregroundDecoration), + 'width': constraints?['minWidth'], + 'height': constraints?['minHeight'], + 'constraints': constraints, + 'margin': ArgumentEncoders.edgeInsets(widget.margin), + 'transform': ArgumentEncoders.matrix(widget.transform), + 'transformAlignment': + ArgumentEncoders.alignment(widget.transformAlignment), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class DefaultTextStyleVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(DefaultTextStyle widget) { + return ConstructorCall( + 'DefaultTextStyle', + NotNullMap.from({ + 'style': ArgumentEncoders.textStyle(widget.style), + 'textAlign': ArgumentEncoders.enumValue(widget.textAlign), + 'softWrap': widget.softWrap, + 'overflow': ArgumentEncoders.enumValue(widget.overflow), + 'maxLines': widget.maxLines, + 'textWidthBasis': + ArgumentEncoders.enumValue(widget.textWidthBasis), + 'textHeightBehavior': + ArgumentEncoders.textHeightBehavior(widget.textHeightBehavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class DirectionalityVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Directionality widget) { + return ConstructorCall( + 'Directionality', + NotNullMap.from({ + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class ExpandedVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Expanded widget) { + return ConstructorCall( + 'Expanded', + NotNullMap.from({ + 'flex': widget.flex, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class FittedBoxVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(FittedBox widget) { + return ConstructorCall( + 'FittedBox', + NotNullMap.from({ + 'fit': ArgumentEncoders.enumValue(widget.fit), + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class FractionallySizedBoxVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(FractionallySizedBox widget) { + return ConstructorCall( + 'FractionallySizedBox', + NotNullMap.from({ + 'widthFactor': widget.widthFactor, + 'heightFactor': widget.heightFactor, + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +/// TODO: EventHandler not implemented right +class GestureDetectorVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(GestureDetector widget) { + return ConstructorCall( + 'GestureDetector', + NotNullMap.from({ + 'onTap': DataSourceEncoder.voidHandler(widget.onTap), + 'onTapDown': DataSourceEncoder.handler(widget.onTapDown), + 'onTapUp': DataSourceEncoder.handler(widget.onTapUp), + 'onTapCancel': DataSourceEncoder.voidHandler(widget.onTapCancel), + 'onDoubleTap': DataSourceEncoder.voidHandler(widget.onDoubleTap), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'behavior': + ArgumentEncoders.enumValue(widget.behavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +// TODO: not implemented +class GridViewVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(GridView widget) { + SliverChildDelegate delegate = widget.childrenDelegate; + Map props = {}; + if (delegate is SliverChildListDelegate) { + props = NotNullMap.from({ + 'children': ArgumentEncoders.widgetList(delegate.children), + 'addAutomaticKeepAlives': delegate.addAutomaticKeepAlives, + 'addRepaintBoundaries': delegate.addRepaintBoundaries, + 'addSemanticIndexes': delegate.addSemanticIndexes, + }); + } else if (delegate is SliverChildBuilderDelegate) { + props = NotNullMap.from({ + // 'children': ArgumentEncoders.widgetList(delegate.builder), + 'addAutomaticKeepAlives': delegate.addAutomaticKeepAlives, + 'addRepaintBoundaries': delegate.addRepaintBoundaries, + 'addSemanticIndexes': delegate.addSemanticIndexes, + }); + } + + return ConstructorCall( + 'GridView', + NotNullMap.from({ + ...props, + 'scrollDirection': + ArgumentEncoders.enumValue(widget.scrollDirection), + 'reverse': widget.reverse, + 'primary': widget.primary, + 'shrinkWrap': widget.shrinkWrap, + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'gridDelegate': ArgumentEncoders.gridDelegate(widget.gridDelegate), + 'cacheExtent': widget.cacheExtent, + 'semanticChildCount': widget.semanticChildCount, + 'dragStartBehavior': ArgumentEncoders.enumValue( + widget.dragStartBehavior), + 'keyboardDismissBehavior': + ArgumentEncoders.enumValue( + widget.keyboardDismissBehavior), + 'restorationId': ArgumentEncoders.string(widget.restorationId), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + }), + ); + } +} + +class IconVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Icon widget) { + return ConstructorCall( + 'Icon', + NotNullMap.from({ + ...?ArgumentEncoders.iconData(widget.icon), + 'size': widget.size, + 'color': widget.color, + 'semanticLabel': ArgumentEncoders.string(widget.semanticLabel), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + })); + } +} + +class IconThemeVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(IconTheme widget) { + return ConstructorCall( + 'IconTheme', + NotNullMap.from({ + ...?ArgumentEncoders.iconThemeData(widget.data), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class IntrinsicHeightVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(IntrinsicHeight widget) { + return ConstructorCall( + 'IntrinsicHeight', + NotNullMap.from({ + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class IntrinsicWidthVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(IntrinsicWidth widget) { + return ConstructorCall( + 'IntrinsicWidth', + NotNullMap.from({ + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class ImageVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Image widget) { + return ConstructorCall( + 'Image', + NotNullMap.from({ + ...?ArgumentEncoders.imageProvider(widget.image), + 'semanticLabel': ArgumentEncoders.string(widget.semanticLabel), + 'excludeFromSemantics': widget.excludeFromSemantics, + 'width': widget.width, + 'height': widget.height, + 'color': ArgumentEncoders.color(widget.color), + 'blendMode': + ArgumentEncoders.enumValue(widget.colorBlendMode), + 'fit': ArgumentEncoders.enumValue(widget.fit), + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'repeat': ArgumentEncoders.enumValue(widget.repeat), + 'centerSlice': ArgumentEncoders.rect(widget.centerSlice), + 'matchTextDirection': widget.matchTextDirection, + 'gaplessPlayback': widget.gaplessPlayback, + 'isAntiAlias': widget.isAntiAlias, + 'filterQuality': + ArgumentEncoders.enumValue(widget.filterQuality), + }), + ); + } +} + +class ListBodyVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(ListBody widget) { + return ConstructorCall( + 'ListBody', + NotNullMap.from({ + 'mainAxis': ArgumentEncoders.enumValue(widget.mainAxis), + 'reverse': widget.reverse, + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} + +class ListViewVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(ListView widget) { + SliverChildDelegate delegate = widget.childrenDelegate; + Map props = NotNullMap(); + if (delegate is SliverChildListDelegate) { + props = NotNullMap.from({ + 'children': ArgumentEncoders.widgetList(delegate.children), + 'addAutomaticKeepAlives': delegate.addAutomaticKeepAlives, + 'addRepaintBoundaries': delegate.addRepaintBoundaries, + 'addSemanticIndexes': delegate.addSemanticIndexes, + }); + } else if (delegate is SliverChildBuilderDelegate) { + props = NotNullMap.from({ + // TODO: not implemented + // 'children': delegate. + 'addAutomaticKeepAlives': delegate.addAutomaticKeepAlives, + 'addRepaintBoundaries': delegate.addRepaintBoundaries, + 'addSemanticIndexes': delegate.addSemanticIndexes, + }); + } + return ConstructorCall( + 'ListView', + NotNullMap.from({ + ...props, + 'scrollDirection': + ArgumentEncoders.enumValue(widget.scrollDirection), + 'reverse': widget.reverse, + 'primary': widget.primary, + 'shrinkWrap': widget.shrinkWrap, + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'itemExtent': widget.itemExtent, + 'prototypeItem': ArgumentEncoders.widget(widget.prototypeItem), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'cacheExtent': widget.cacheExtent, + 'semanticChildCount': widget.semanticChildCount, + 'dragStartBehavior': ArgumentEncoders.enumValue( + widget.dragStartBehavior), + 'keyboardDismissBehavior': + ArgumentEncoders.enumValue( + widget.keyboardDismissBehavior), + 'restorationId': ArgumentEncoders.string(widget.restorationId), + })); + } +} + +class OpacityVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Opacity widget) { + return ConstructorCall( + 'Opacity', + NotNullMap.from({ + 'opacity': widget.opacity, + 'alwaysIncludeSemantics': widget.alwaysIncludeSemantics, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class PaddingVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Padding widget) { + return ConstructorCall( + 'Padding', + NotNullMap.from({ + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class PlaceholderVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Placeholder widget) { + return ConstructorCall( + 'Placeholder', + NotNullMap.from({ + 'color': ArgumentEncoders.color(widget.color), + 'strokeWidth': widget.strokeWidth, + 'fallbackWidth': widget.fallbackWidth, + 'fallbackHeight': widget.fallbackHeight, + })); + } +} + +class PositionedVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Positioned widget) { + return ConstructorCall( + 'Positioned', + NotNullMap.from({ + 'start': widget.left, + 'top': widget.top, + 'end': widget.right, + 'bottom': widget.bottom, + 'width': widget.width, + 'height': widget.height, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class RotationVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedRotation widget) { + return ConstructorCall( + 'Rotation', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'turns': widget.turns, + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'filterQuality': + ArgumentEncoders.enumValue(widget.filterQuality), + 'child': ArgumentEncoders.widget(widget.child), + 'onEnd': widget.onEnd + })); + } +} + +class RowVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Row widget) { + return ConstructorCall( + 'Row', + NotNullMap.from({ + 'mainAxisAlignment': ArgumentEncoders.enumValue( + widget.mainAxisAlignment), + 'mainAxisSize': + ArgumentEncoders.enumValue(widget.mainAxisSize), + 'crossAxisAlignment': ArgumentEncoders.enumValue( + widget.crossAxisAlignment), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'verticalDirection': ArgumentEncoders.enumValue( + widget.verticalDirection), + 'textBaseline': + ArgumentEncoders.enumValue(widget.textBaseline), + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} + +class SafeAreaVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(SafeArea widget) { + return ConstructorCall( + 'SafeArea', + NotNullMap.from({ + 'left': widget.left, + 'top': widget.top, + 'right': widget.right, + 'bottom': widget.bottom, + 'minimum': ArgumentEncoders.edgeInsets(widget.minimum), + 'maintainBottomViewPadding': widget.maintainBottomViewPadding, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class ScaleVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(AnimatedScale widget) { + return ConstructorCall( + 'Scale', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'scale': widget.scale, + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'filterQuality': + ArgumentEncoders.enumValue(widget.filterQuality), + 'child': ArgumentEncoders.widget(widget.child), + 'onEnd': widget.onEnd + })); + } +} + +class SingleChildScrollViewVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(SingleChildScrollView widget) { + return ConstructorCall( + 'SingleChildScrollView', + NotNullMap.from({ + 'scrollDirection': + ArgumentEncoders.enumValue(widget.scrollDirection), + 'reverse': widget.reverse, + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'primary': widget.primary, + 'dragStartBehavior': ArgumentEncoders.enumValue( + widget.dragStartBehavior), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'restorationId': ArgumentEncoders.string(widget.restorationId), + 'keyboardDismissBehavior': + ArgumentEncoders.enumValue( + widget.keyboardDismissBehavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class SizedBoxVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(SizedBox widget) { + if (widget.width == double.infinity && widget.width == double.infinity) { + return ConstructorCall('SizedBoxExpand', + NotNullMap.from({'child': ArgumentEncoders.widget(widget.child)})); + } + if (widget.width == 0 && widget.height == 0) { + return ConstructorCall('SizedBoxShrink', + NotNullMap.from({'child': ArgumentEncoders.widget(widget.child)})); + } + + return ConstructorCall( + 'SizedBox', + NotNullMap.from({ + 'width': widget.width, + 'height': widget.height, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class SpacerVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Spacer widget) { + return ConstructorCall('Spacer', {'flex': widget.flex}); + } +} + +class StackVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Stack widget) { + return ConstructorCall( + 'Stack', + NotNullMap.from({ + 'alignment': ArgumentEncoders.alignment(widget.alignment), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'fit': ArgumentEncoders.enumValue(widget.fit), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} + +class TextVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Text widget) { + double? fontSize = widget.style?.fontSize; + return ConstructorCall( + 'Text', + NotNullMap.from({ + 'text': ArgumentEncoders.string(widget.data), + 'style': ArgumentEncoders.textStyle(widget.style), + 'strutStyle': ArgumentEncoders.strutStyle(widget.strutStyle), + 'textAlign': ArgumentEncoders.enumValue(widget.textAlign), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'locale': ArgumentEncoders.locale(widget.locale), + 'softWrap': widget.softWrap, + 'overflow': ArgumentEncoders.enumValue(widget.overflow), + // TODO: not verified yet + 'textScaleFactor': widget.textScaler == TextScaler.noScaling + ? null + : fontSize != null + ? widget.textScaler?.scale(fontSize) + : null, + 'maxLines': widget.maxLines, + 'semanticsLabel': ArgumentEncoders.string(widget.semanticsLabel), + 'textWidthBasis': + ArgumentEncoders.enumValue(widget.textWidthBasis), + 'textHeightBehavior': + ArgumentEncoders.textHeightBehavior(widget.textHeightBehavior), + })); + } +} + +class WrapVisitor implements CoreWidgetVisitor { + @override + ConstructorCall visit(Wrap widget) { + return ConstructorCall( + 'Wrap', + NotNullMap.from({ + 'direction': ArgumentEncoders.enumValue(widget.direction), + 'alignment': + ArgumentEncoders.enumValue(widget.alignment), + 'spacing': widget.spacing, + 'runAlignment': + ArgumentEncoders.enumValue(widget.runAlignment), + 'runSpacing': widget.runSpacing, + 'crossAxisAlignment': ArgumentEncoders.enumValue( + widget.crossAxisAlignment), + 'textDirection': + ArgumentEncoders.enumValue(widget.textDirection), + 'verticalDirection': ArgumentEncoders.enumValue( + widget.verticalDirection), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} diff --git a/packages/widget2rfw/lib/flutter/material_widgets.dart b/packages/widget2rfw/lib/flutter/material_widgets.dart new file mode 100644 index 0000000..37c120a --- /dev/null +++ b/packages/widget2rfw/lib/flutter/material_widgets.dart @@ -0,0 +1,524 @@ +import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; +import 'package:rfw/rfw.dart'; + +import 'arguments_encoder.dart'; +import 'runtime.dart'; + +Map materialVisitorMap = { + AboutListTile: AboutListTileVisitor(), + AppBar: AppBarVisitor(), + ButtonBar: ButtonBarVisitor(), + OverflowBar: OverflowBarVisitor(), + Card: CardVisitor(), + CircularProgressIndicator: CircularProgressIndicatorVisitor(), + Divider: DividerVisitor(), + Drawer: DrawerVisitor(), + DrawerHeader: DrawerHeaderVisitor(), + DropdownButton: DropdownButtonVisitor(), + ElevatedButton: ElevatedButtonVisitor(), + FloatingActionButton: FloatingActionButtonVisitor(), + InkResponse: InkResponseVisitor(), + InkWell: InkWellVisitor(), + LinearProgressIndicator: LinearProgressIndicatorVisitor(), + ListTile: ListTileVisitor(), + Material: MaterialVisitor(), + OutlinedButton: OutlinedButtonVisitor(), + Scaffold: ScaffoldVisitor(), + TextButton: TextButtonVisitor(), + VerticalDivider: VerticalDividerVisitor(), +}; + +sealed class MaterialWidgetVisitor extends WidgetVisitor { + @override + ConstructorCall visit(covariant Widget widget); +} + +class AboutListTileVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(AboutListTile widget) { + return ConstructorCall( + 'AboutListTile', + NotNullMap.from({ + 'icon': ArgumentEncoders.widget(widget.icon), + 'applicationName': ArgumentEncoders.string(widget.applicationName), + 'applicationVersion': + ArgumentEncoders.string(widget.applicationVersion), + 'applicationIcon': ArgumentEncoders.widget(widget.applicationIcon), + 'applicationLegalese': + ArgumentEncoders.string(widget.applicationLegalese), + 'aboutBoxChildren': + ArgumentEncoders.widgetList(widget.aboutBoxChildren), + 'dense': widget.dense, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class AppBarVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(AppBar widget) { + return ConstructorCall( + 'AppBar', + NotNullMap.from({ + 'leading': ArgumentEncoders.widget(widget.leading), + 'automaticallyImplyLeading': widget.automaticallyImplyLeading, + 'actions': ArgumentEncoders.widgetList(widget.actions), + 'shadowColor': ArgumentEncoders.color(widget.shadowColor), + 'shape': ArgumentEncoders.shapeBorder(widget.shape), + 'backgroundColor': ArgumentEncoders.color(widget.backgroundColor), + 'foregroundColor': ArgumentEncoders.color(widget.foregroundColor), + 'iconTheme': ArgumentEncoders.iconThemeData(widget.iconTheme), + 'actionsIconTheme': + ArgumentEncoders.iconThemeData(widget.actionsIconTheme), + 'primary': widget.primary, + 'centerTitle': widget.centerTitle, + 'excludeHeaderSemantics': widget.excludeHeaderSemantics, + 'titleSpacing': widget.titleSpacing, + 'toolbarOpacity': widget.toolbarOpacity, + 'toolbarHeight': widget.toolbarHeight, + 'leadingWidth': widget.leadingWidth, + 'toolbarTextStyle': + ArgumentEncoders.textStyle(widget.toolbarTextStyle), + 'titleTextStyle': ArgumentEncoders.textStyle(widget.titleTextStyle), + })); + } +} + +class ButtonBarVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(ButtonBar widget) { + return ConstructorCall( + 'ButtonBar', + NotNullMap.from({ + 'buttonPadding': ArgumentEncoders.edgeInsets(widget.buttonPadding), + 'layoutBehavior': widget.layoutBehavior, + 'alignment': ArgumentEncoders.enumValue(widget.alignment), + // TODO: not implemented + // 'spacing': widget. + 'overflowDirection': + ArgumentEncoders.enumValue(widget.overflowDirection), + 'overflowButtonSpacing': widget.overflowButtonSpacing, + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} + +class OverflowBarVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(OverflowBar widget) { + return ConstructorCall( + 'OverflowBar', + NotNullMap.from({ + 'alignment': ArgumentEncoders.enumValue(widget.alignment), + 'spacing': widget.spacing, + 'overflowDirection': + ArgumentEncoders.enumValue(widget.overflowDirection), + 'overflowSpacing': widget.overflowSpacing, + 'children': ArgumentEncoders.widgetList(widget.children), + })); + } +} + +class CardVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(Card widget) { + return ConstructorCall( + 'Card', + NotNullMap.from({ + 'color': ArgumentEncoders.color(widget.color), + 'shadowColor': ArgumentEncoders.color(widget.shadowColor), + 'elevation': widget.elevation, + 'margin': ArgumentEncoders.edgeInsets(widget.margin), + 'shape': ArgumentEncoders.shapeBorder(widget.shape), + 'borderOnForeground': widget.borderOnForeground, + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'semanticContainer': widget.semanticContainer, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class CircularProgressIndicatorVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(CircularProgressIndicator widget) { + return ConstructorCall( + 'CircularProgressIndicator', + NotNullMap.from({ + 'value': widget.value, + 'color': ArgumentEncoders.color(widget.color), + 'backgroundColor': ArgumentEncoders.color(widget.backgroundColor), + 'strokeWidth': widget.strokeWidth, + 'semanticsLabel': ArgumentEncoders.string(widget.semanticsLabel), + 'semanticsValue': ArgumentEncoders.string(widget.semanticsValue), + })); + } +} + +class DividerVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(Divider widget) { + return ConstructorCall( + 'Divider', + NotNullMap.from({ + 'height': widget.height, + 'thickness': widget.thickness, + 'indent': widget.indent, + 'endIndent': widget.endIndent, + 'color': ArgumentEncoders.color(widget.color), + })); + } +} + +class DrawerVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(Drawer widget) { + return ConstructorCall( + 'Drawer', + NotNullMap.from({ + 'elevation': widget.elevation, + 'semanticLabel': ArgumentEncoders.string(widget.semanticLabel), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class DrawerHeaderVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(DrawerHeader widget) { + return ConstructorCall( + 'DrawerHeader', + NotNullMap.from({ + 'duration': ArgumentEncoders.duration(widget.duration), + 'curve': ArgumentEncoders.curve(widget.curve), + 'decoration': ArgumentEncoders.decoration(widget.decoration), + 'margin': ArgumentEncoders.edgeInsets(widget.margin), + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class DropdownButtonVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(DropdownButton widget) { + List? items = widget.items?.mapIndexed((index, item) { + return NotNullMap.from({ + 'onTap': DataSourceEncoder.voidHandler(item.onTap), + 'value': item.value, + 'enabled': item.enabled, + 'alignment': ArgumentEncoders.enumValue(item.alignment), + 'child': ArgumentEncoders.widget(item.child), + }); + }).toList(); + return ConstructorCall( + 'DropdownButton', + NotNullMap.from({ + 'items': items, + 'value': widget.value, + 'disabledHint': ArgumentEncoders.widget(widget.disabledHint), + 'onChanged': DataSourceEncoder.handler(widget.onChanged), + 'onTap': DataSourceEncoder.voidHandler(widget.onTap), + 'elevation': widget.elevation, + 'style': ArgumentEncoders.textStyle(widget.style), + 'underline': ArgumentEncoders.widget(widget.underline), + 'icon': ArgumentEncoders.widget(widget.icon), + 'iconDisabledColor': ArgumentEncoders.color(widget.iconDisabledColor), + 'iconEnabledColor': ArgumentEncoders.color(widget.iconEnabledColor), + 'iconSize': widget.iconSize, + 'isDense': widget.isDense, + 'isExpanded': widget.isExpanded, + 'itemHeight': widget.itemHeight, + 'focusColor': ArgumentEncoders.color(widget.focusColor), + 'autofocus': widget.autofocus, + 'dropdownColor': ArgumentEncoders.color(widget.dropdownColor), + 'menuMaxHeight': widget.menuMaxHeight, + 'enableFeedback': widget.enableFeedback, + 'alignment': ArgumentEncoders.enumValue(widget.alignment), + 'borderRadius': ArgumentEncoders.borderRadius(widget.borderRadius), + 'padding': ArgumentEncoders.edgeInsets(widget.padding), + })); + } +} + +class ElevatedButtonVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(ElevatedButton widget) { + return ConstructorCall( + 'ElevatedButton', + NotNullMap.from({ + 'onPressed': DataSourceEncoder.voidHandler(widget.onPressed), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'autofocus': widget.autofocus, + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class FloatingActionButtonVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(FloatingActionButton widget) { + return ConstructorCall( + 'FloatingActionButton', + NotNullMap.from({ + 'tooltip': ArgumentEncoders.string(widget.tooltip), + 'foregroundColor': ArgumentEncoders.color(widget.foregroundColor), + 'backgroundColor': ArgumentEncoders.color(widget.backgroundColor), + 'focusColor': ArgumentEncoders.color(widget.focusColor), + 'hoverColor': ArgumentEncoders.color(widget.hoverColor), + 'splashColor': ArgumentEncoders.color(widget.splashColor), + 'heroTag': ArgumentEncoders.string(widget.heroTag?.toString()), + 'elevation': widget.elevation, + 'focusElevation': widget.focusElevation, + 'hoverElevation': widget.hoverElevation, + 'highlightElevation': widget.highlightElevation, + 'disabledElevation': widget.disabledElevation, + 'onPressed': DataSourceEncoder.voidHandler(widget.onPressed), + 'mini': widget.mini, + 'shape': ArgumentEncoders.shapeBorder(widget.shape), + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'autofocus': widget.autofocus, + 'materialTapTargetSize': + ArgumentEncoders.enumValue(widget.materialTapTargetSize), + 'isExtended': widget.isExtended, + 'enableFeedback': widget.enableFeedback, + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class InkResponseVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(InkResponse widget) { + return ConstructorCall( + 'InkResponse', + NotNullMap.from({ + 'onTap': DataSourceEncoder.voidHandler(widget.onTap), + 'onTapDown': DataSourceEncoder.handler(widget.onTapDown), + 'onTapUp': DataSourceEncoder.handler(widget.onTapUp), + 'onTapCancel': DataSourceEncoder.voidHandler(widget.onTapCancel), + 'onDoubleTap': DataSourceEncoder.voidHandler(widget.onDoubleTap), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'onSecondaryTap': + DataSourceEncoder.voidHandler(widget.onSecondaryTap), + 'onSecondaryTapUp': + DataSourceEncoder.handler(widget.onSecondaryTapUp), + 'onSecondaryTapDown': + DataSourceEncoder.handler(widget.onSecondaryTapDown), + 'onSecondaryTapCancel': + DataSourceEncoder.voidHandler(widget.onSecondaryTapCancel), + 'onHighlightChanged': + DataSourceEncoder.handler(widget.onHighlightChanged), + 'onHover': DataSourceEncoder.handler(widget.onHover), + 'containedInkWell': widget.containedInkWell, + 'highlightShape': ArgumentEncoders.enumValue(widget.highlightShape), + 'radius': widget.radius, + 'borderRadius': ArgumentEncoders.borderRadius(widget.borderRadius), + 'customBorder': ArgumentEncoders.shapeBorder(widget.customBorder), + 'focusColor': ArgumentEncoders.color(widget.focusColor), + 'hoverColor': ArgumentEncoders.color(widget.hoverColor), + 'highlightColor': ArgumentEncoders.color(widget.highlightColor), + 'splashColor': ArgumentEncoders.color(widget.splashColor), + 'enableFeedback': widget.enableFeedback, + 'excludeFromSemantics': widget.excludeFromSemantics, + 'canRequestFocus': widget.canRequestFocus, + 'onFocusChange': DataSourceEncoder.handler(widget.onFocusChange), + 'autofocus': widget.autofocus, + 'hoverDuration': ArgumentEncoders.duration(widget.hoverDuration), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class InkWellVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(InkWell widget) { + return ConstructorCall( + 'InkWell', + NotNullMap.from({ + 'onTap': DataSourceEncoder.voidHandler(widget.onTap), + 'onDoubleTap': DataSourceEncoder.voidHandler(widget.onDoubleTap), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'onTapDown': DataSourceEncoder.handler(widget.onTapDown), + 'onTapCancel': DataSourceEncoder.voidHandler(widget.onTapCancel), + 'onTapUp': DataSourceEncoder.handler(widget.onTapUp), + 'onSecondaryTap': + DataSourceEncoder.voidHandler(widget.onSecondaryTap), + 'onSecondaryTapUp': + DataSourceEncoder.handler(widget.onSecondaryTapUp), + 'onSecondaryTapDown': + DataSourceEncoder.handler(widget.onSecondaryTapDown), + 'onSecondaryTapCancel': + DataSourceEncoder.voidHandler(widget.onSecondaryTapCancel), + 'onHighlightChanged': + DataSourceEncoder.handler(widget.onHighlightChanged), + 'onHover': DataSourceEncoder.handler(widget.onHover), + 'focusColor': ArgumentEncoders.color(widget.focusColor), + 'hoverColor': ArgumentEncoders.color(widget.hoverColor), + 'highlightColor': ArgumentEncoders.color(widget.highlightColor), + 'splashColor': ArgumentEncoders.color(widget.splashColor), + 'radius': widget.radius, + 'borderRadius': ArgumentEncoders.borderRadius(widget.borderRadius), + 'customBorder': ArgumentEncoders.shapeBorder(widget.customBorder), + 'enableFeedback': widget.enableFeedback, + 'excludeFromSemantics': widget.excludeFromSemantics, + 'autofocus': widget.autofocus, + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class LinearProgressIndicatorVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(LinearProgressIndicator widget) { + return ConstructorCall( + 'LinearProgressIndicator', + NotNullMap.from({ + 'value': widget.value, + 'color': ArgumentEncoders.color(widget.color), + 'backgroundColor': ArgumentEncoders.color(widget.backgroundColor), + 'minHeight': widget.minHeight, + 'semanticsLabel': ArgumentEncoders.string(widget.semanticsLabel), + 'semanticsValue': ArgumentEncoders.string(widget.semanticsValue), + })); + } +} + +class ListTileVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(ListTile widget) { + return ConstructorCall( + 'ListTile', + NotNullMap.from({ + 'leading': ArgumentEncoders.widget(widget.leading), + 'title': ArgumentEncoders.widget(widget.title), + 'subtitle': ArgumentEncoders.widget(widget.subtitle), + 'trailing': ArgumentEncoders.widget(widget.trailing), + 'isThreeLine': widget.isThreeLine, + 'dense': widget.dense, + 'visualDensity': ArgumentEncoders.visualDensity(widget.visualDensity), + 'shape': ArgumentEncoders.shapeBorder(widget.shape), + 'contentPadding': ArgumentEncoders.edgeInsets(widget.contentPadding), + 'enabled': widget.enabled, + 'onTap': DataSourceEncoder.voidHandler(widget.onTap), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'selected': widget.selected, + 'focusColor': ArgumentEncoders.color(widget.focusColor), + 'hoverColor': ArgumentEncoders.color(widget.hoverColor), + 'autofocus': widget.autofocus, + 'tileColor': ArgumentEncoders.color(widget.tileColor), + 'selectedTileColor': ArgumentEncoders.color(widget.selectedTileColor), + 'enableFeedback': widget.enableFeedback, + 'horizontalTitleGap': widget.horizontalTitleGap, + 'minVerticalPadding': widget.minVerticalPadding, + 'minLeadingWidth': widget.minLeadingWidth, + }), + ); + } +} + +class MaterialVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(Material widget) { + return ConstructorCall( + 'Material', + NotNullMap.from({ + 'type': ArgumentEncoders.enumValue(widget.type), + 'elevation': widget.elevation, + 'color': ArgumentEncoders.color(widget.color), + 'shadowColor': ArgumentEncoders.color(widget.shadowColor), + 'surfaceTintColor': ArgumentEncoders.color(widget.surfaceTintColor), + 'textStyle': ArgumentEncoders.textStyle(widget.textStyle), + 'borderRadius': ArgumentEncoders.borderRadius(widget.borderRadius), + 'shape': ArgumentEncoders.shapeBorder(widget.shape), + 'borderOnForeground': widget.borderOnForeground, + 'clipBehavior': widget.clipBehavior, + 'animationDuration': + ArgumentEncoders.duration(widget.animationDuration), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class OutlinedButtonVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(OutlinedButton widget) { + return ConstructorCall( + 'OutlinedButton', + NotNullMap.from({ + 'onPressed': DataSourceEncoder.voidHandler(widget.onPressed), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'autofocus': widget.autofocus, + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + }), + ); + } +} + +class ScaffoldVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(Scaffold widget) { + return ConstructorCall( + 'Scaffold', + NotNullMap.from({ + 'appBar': ArgumentEncoders.widget(widget.appBar), + 'body': ArgumentEncoders.widget(widget.body), + 'floatingActionButton': + ArgumentEncoders.widget(widget.floatingActionButton), + 'persistentFooterButtons': + ArgumentEncoders.widgetList(widget.persistentFooterButtons), + 'drawer': ArgumentEncoders.widget(widget.drawer), + 'endDrawer': ArgumentEncoders.widget(widget.endDrawer), + 'bottomNavigationBar': + ArgumentEncoders.widget(widget.bottomNavigationBar), + 'bottomSheet': ArgumentEncoders.widget(widget.bottomSheet), + 'resizeToAvoidBottomInset': widget.resizeToAvoidBottomInset, + 'primary': widget.primary, + 'drawerDragStartBehavior': + ArgumentEncoders.enumValue(widget.drawerDragStartBehavior), + 'extendBody': widget.extendBody, + 'extendBodyBehindAppBar': widget.extendBodyBehindAppBar, + 'drawerScrimColor': ArgumentEncoders.color(widget.drawerScrimColor), + 'drawerEdgeDragWidth': widget.drawerEdgeDragWidth, + 'drawerEnableOpenDragGesture': widget.drawerEnableOpenDragGesture, + 'endDrawerEnableOpenDragGesture': widget.endDrawerEnableOpenDragGesture, + 'restorationId': ArgumentEncoders.string(widget.restorationId), + }), + ); + } +} + +class TextButtonVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(TextButton widget) { + return ConstructorCall( + 'TextButton', + NotNullMap.from({ + 'onPressed': DataSourceEncoder.voidHandler(widget.onPressed), + 'onLongPress': DataSourceEncoder.voidHandler(widget.onLongPress), + 'autofocus': widget.autofocus, + 'clipBehavior': ArgumentEncoders.enumValue(widget.clipBehavior), + 'child': ArgumentEncoders.widget(widget.child), + })); + } +} + +class VerticalDividerVisitor implements MaterialWidgetVisitor { + @override + ConstructorCall visit(VerticalDivider widget) { + return ConstructorCall( + 'VerticalDivider', + NotNullMap.from({ + 'width': widget.width, + 'thickness': widget.thickness, + 'indent': widget.indent, + 'endIndent': widget.endIndent, + 'color': ArgumentEncoders.color(widget.color), + })); + } +} diff --git a/packages/widget2rfw/lib/flutter/runtime.dart b/packages/widget2rfw/lib/flutter/runtime.dart new file mode 100644 index 0000000..9290572 --- /dev/null +++ b/packages/widget2rfw/lib/flutter/runtime.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; + +import 'package:rfw/rfw.dart'; + +abstract class DataSourceEncoder { + /// Return the int, double, bool, or String value at the given path of the + /// arguments to the widget. + /// + /// `T` must be one of [int], [double], [bool], or [String]. + /// + /// If `T` does not match the type of the value obtained, then the method + /// returns null. + T? v(List argsKey); + + /// Return true if the given key identifies a list, otherwise false. + bool isList(List argsKey); + + /// Return the length of the list at the given path of the arguments to the + /// widget. + /// + /// If the given path does not identify a list, returns zero. + int length(List argsKey); + + /// Return true if the given key identifies a map, otherwise false. + bool isMap(List argsKey); + + /// Build the child at the given key. + /// + /// If the node specified is not a widget, returns an [ErrorWidget]. + /// + /// See also: + /// + /// * [optionalChild], which returns null if the widget is missing. + Widget child(List argsKey); + + /// Build the child at the given key. + /// + /// If the node specified is not a widget, returns null. + /// + /// See also: + /// + /// * [child], which returns an [ErrorWidget] instead of null if the widget + /// is missing. + Widget? optionalChild(List argsKey); + + /// Builds the children at the given key. + /// + /// If the node is missing, returns an empty list. + /// + /// If the node specified is not a list of widgets, returns a list with the + /// non-widget nodes replaced by [ErrorWidget]. + List childList(List argsKey); + + /// Builds the widget builder at the given key. + /// + /// If the node is not a widget builder, returns an [ErrorWidget]. + /// + /// See also: + /// + /// * [optionalBuilder], which returns null if the widget builder is missing. + Widget builder(List argsKey, DynamicMap builderArg); + + /// Builds the widget builder at the given key. + /// + /// If the node is not a widget builder, returns null. + /// + /// See also: + /// + /// * [builder], which returns an [ErrorWidget] instead of null if the widget + /// builder is missing. + Widget? optionalBuilder(List argsKey, DynamicMap builderArg); + + /// Gets a [VoidCallback] event handler at the given key. + /// + /// If the node specified is an [AnyEventHandler] or a [DynamicList] of + /// [AnyEventHandler]s, returns a callback that invokes the specified event + /// handler(s), merging the given `extraArguments` into the arguments + /// specified in each event handler. In the event of a key conflict (where + /// both the arguments specified in the remote widget declaration and the + /// argument provided to this method have the same name), the arguments + /// specified here take precedence. + static dynamic voidHandler(VoidCallback? callback) { + if (callback == null) { + return null; + } + throw 'Not implemented'; + } + + /// Gets an event handler at the given key. + /// + /// The event handler can be of any Function type, as specified by the type + /// argument `T`. + /// + /// When this method is called, the second argument, `generator`, is invoked. + /// The `generator` callback must return a function, which we will call + /// _entrypoint_, that matches the signature of `T`. The `generator` callback + /// receives an argument, which we will call `trigger`. The _entrypoint_ + /// function must call `trigger`, optionally passing it any extra arguments + /// that should be merged into the arguments specified in each event handler. + /// + /// This is admittedly a little confusing. At its core, the problem is that + /// this method cannot itself automatically create a function (_entrypoint_) + /// of the right type (`T`), and therefore a callback (`generator`) that knows + /// how to wrap a function body (`trigger`) in the right signature (`T`) is + /// needed to actually build that function (_entrypoint_). + static T? handler(void Function(S)? callback) { + if (callback == null) { + return null; + } + throw 'Not implemented'; + } +} diff --git a/packages/widget2rfw/lib/widget2rfw.dart b/packages/widget2rfw/lib/widget2rfw.dart new file mode 100644 index 0000000..f0d591e --- /dev/null +++ b/packages/widget2rfw/lib/widget2rfw.dart @@ -0,0 +1 @@ +library widget2rfw; diff --git a/packages/widget2rfw/pubspec.lock b/packages/widget2rfw/pubspec.lock new file mode 100644 index 0000000..07866b9 --- /dev/null +++ b/packages/widget2rfw/pubspec.lock @@ -0,0 +1,245 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + url: "https://pub.dev" + source: hosted + version: "3.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + import_sorter: + dependency: "direct dev" + description: + name: import_sorter + sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 + url: "https://pub.dev" + source: hosted + version: "4.6.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + rfw: + dependency: "direct main" + description: + name: rfw + sha256: "079eb640f510b2b0e606dd011e6ccf438d39def206133bca400ba19bc0074667" + url: "https://pub.dev" + source: hosted + version: "1.0.26" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + tint: + dependency: transitive + description: + name: tint + sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.3.1 <4.0.0" + flutter: ">=3.16.0" diff --git a/packages/widget2rfw/pubspec.yaml b/packages/widget2rfw/pubspec.yaml new file mode 100644 index 0000000..a3a8e41 --- /dev/null +++ b/packages/widget2rfw/pubspec.yaml @@ -0,0 +1,21 @@ +name: widget2rfw +description: "A new Flutter package project." +version: 0.0.1 +homepage: https://github.com/ebwood/rfwt + +environment: + sdk: '>=3.3.1 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + collection: ^1.18.0 + flutter: + sdk: flutter + rfw: ^1.0.26 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + import_sorter: ^4.6.0 +