Add rfw widgets (#5661)

Add two rfw widgets(ClipRRect and DropdownButton) and `flutter format`
the widget files.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [relevant style guides] and ran the
auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages
repo does use `dart format`.)
- [x] I signed the [CLA].
- [x] The title of the PR starts with the name of the package surrounded
by square brackets, e.g. `[shared_preferences]`
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated `pubspec.yaml` with an appropriate new version according
to the [pub versioning philosophy], or this PR is [exempt from version
changes].
- [x] I updated `CHANGELOG.md` to add a description of the change,
[following repository CHANGELOG style].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[relevant style guides]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[exempt from version changes]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#version-and-changelog-updates
[following repository CHANGELOG style]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changelog-style
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
diff --git a/packages/rfw/CHANGELOG.md b/packages/rfw/CHANGELOG.md
index 99ff72e..1998c2c 100644
--- a/packages/rfw/CHANGELOG.md
+++ b/packages/rfw/CHANGELOG.md
@@ -1,5 +1,7 @@
-## 1.0.18
+## 1.0.19
+* Add `DropdownButton` and `ClipRRect` widgets to rfw widget library.
 
+## 1.0.18
 * Exposes `WidgetLibrary`s registered in `Runtime`.
 * Exposes widgets map in `LocalWidgetLibrary`.
 
diff --git a/packages/rfw/lib/src/flutter/core_widgets.dart b/packages/rfw/lib/src/flutter/core_widgets.dart
index 04f3aca..559d908 100644
--- a/packages/rfw/lib/src/flutter/core_widgets.dart
+++ b/packages/rfw/lib/src/flutter/core_widgets.dart
@@ -27,6 +27,7 @@
 ///  * [Align]
 ///  * [AspectRatio]
 ///  * [Center]
+///  * [ClipRRect]
 ///  * [ColoredBox]
 ///  * [Column]
 ///  * [Container] (actually uses [AnimatedContainer])
@@ -269,6 +270,15 @@
     );
   },
 
+  'ClipRRect': (BuildContext context, DataSource source) {
+    return ClipRRect(
+      borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius']) ?? BorderRadius.zero,
+      // CustomClipper<RRect> clipper,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.antiAlias,
+      child: source.optionalChild(['child']),
+    );
+  }, 
+
   'ColoredBox': (BuildContext context, DataSource source) {
     return ColoredBox(
       color: ArgumentDecoders.color(source, ['color']) ?? const Color(0xFF000000),
diff --git a/packages/rfw/lib/src/flutter/material_widgets.dart b/packages/rfw/lib/src/flutter/material_widgets.dart
index 00a721b..59fb290 100644
--- a/packages/rfw/lib/src/flutter/material_widgets.dart
+++ b/packages/rfw/lib/src/flutter/material_widgets.dart
@@ -27,6 +27,7 @@
 ///  * [CircularProgressIndicator]
 ///  * [Divider]
 ///  * [DrawerHeader]
+///  * [DropdownButton]
 ///  * [ElevatedButton]
 ///  * [FloatingActionButton]
 ///  * [InkWell]
@@ -67,6 +68,11 @@
 ///  * The [Scaffold]'s floating action button position and animation features
 ///    are not supported.
 ///
+///  * [DropdownButton] takes a `items` object which contains a list of
+///    [DropdownMenuItem] configuration objects. Each object may contain
+///    `onTap`, `value`, `enabled` and `child`. The `child` parameter is
+///    required.
+///
 /// In general, the trend will all of these unsupported features is that this
 /// library doesn't support features that can't be trivially expressed using the
 /// JSON-like structures of RFW. For example, [MaterialStateProperty] is
@@ -188,6 +194,46 @@
     );
   },
 
+  'DropdownButton': (BuildContext context, DataSource source) {
+    final int length = source.length(['items']);
+    final List<DropdownMenuItem<Object>> dropdownMenuItems = List<DropdownMenuItem<Object>>.generate(
+      length,
+      (int index) => DropdownMenuItem<Object>(
+        onTap: source.voidHandler(['items', index, 'onTap']),
+        value: source.v<String>(['items', index, 'value']) ?? source.v<int>(['items', index, 'value']) ?? source.v<double>(['items', index, 'value']) ?? source.v<bool>(['items', index, 'value']),
+        enabled: source.v<bool>(['items', index, 'enabled']) ?? true,
+        alignment: ArgumentDecoders.alignment(source, ['items', index, 'alignment']) ?? AlignmentDirectional.centerStart,
+        child: source.child(['items', index, 'child']),
+      ),
+    );
+
+    return DropdownButton<Object>(
+      items: dropdownMenuItems,
+      value: source.v<String>(['value']) ?? source.v<int>(['value']) ?? source.v<double>(['value']) ?? source.v<bool>(['value']),
+      disabledHint: source.optionalChild(['disabledHint']),
+      onChanged: source.handler(<Object>['onChanged'], (HandlerTrigger trigger) => (Object? value) => trigger(<String, Object?>{'value': value})),
+      onTap: source.voidHandler(['onTap']),
+      elevation: source.v<int>(['elevation']) ?? 8,
+      style: ArgumentDecoders.textStyle(source, ['style']),
+      underline: source.optionalChild(['underline']),
+      icon: source.optionalChild(['icon']),
+      iconDisabledColor: ArgumentDecoders.color(source, ['iconDisabledColor']),
+      iconEnabledColor: ArgumentDecoders.color(source, ['iconEnabledColor']),
+      iconSize: source.v<double>(['iconSize']) ?? 24.0,
+      isDense: source.v<bool>(['isDense']) ?? false,
+      isExpanded: source.v<bool>(['isExpanded']) ?? false,
+      itemHeight: source.v<double>(['itemHeight']) ?? kMinInteractiveDimension,
+      focusColor: ArgumentDecoders.color(source, ['focusColor']),
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      dropdownColor: ArgumentDecoders.color(source, ['dropdownColor']),
+      menuMaxHeight: source.v<double>(['menuMaxHeight']),
+      enableFeedback: source.v<bool>(['enableFeedback']),
+      alignment: ArgumentDecoders.alignment(source, ['alignment']) ?? AlignmentDirectional.centerStart,
+      borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)),
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']),
+    );
+  },
+
   'ElevatedButton': (BuildContext context, DataSource source) {
     // not implemented: buttonStyle, focusNode
     return ElevatedButton(
diff --git a/packages/rfw/pubspec.yaml b/packages/rfw/pubspec.yaml
index 0d22097..b683d0d 100644
--- a/packages/rfw/pubspec.yaml
+++ b/packages/rfw/pubspec.yaml
@@ -2,7 +2,7 @@
 description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime."
 repository: https://github.com/flutter/packages/tree/main/packages/rfw
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22
-version: 1.0.18
+version: 1.0.19
 
 environment:
   sdk: ">=3.0.0 <4.0.0"
diff --git a/packages/rfw/test/core_widgets_test.dart b/packages/rfw/test/core_widgets_test.dart
index 8765e13..40521a6 100644
--- a/packages/rfw/test/core_widgets_test.dart
+++ b/packages/rfw/test/core_widgets_test.dart
@@ -7,6 +7,7 @@
 import 'dart:typed_data';
 
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:rfw/formats.dart' show parseLibraryFile;
 import 'package:rfw/rfw.dart';
@@ -278,5 +279,15 @@
     '''));
     await tester.pump();
     expect(find.byType(Wrap), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ClipRRect();
+    '''));
+    await tester.pump();
+    expect(find.byType(ClipRRect), findsOneWidget);
+    final RenderClipRRect renderClip = tester.allRenderObjects.whereType<RenderClipRRect>().first; 
+    expect(renderClip.clipBehavior, equals(Clip.antiAlias));
+    expect(renderClip.borderRadius, equals(BorderRadius.zero));
   });
 }
diff --git a/packages/rfw/test/goldens/material_test.drawer.png b/packages/rfw/test/goldens/material_test.drawer.png
index 4c240b5..38444da 100644
--- a/packages/rfw/test/goldens/material_test.drawer.png
+++ b/packages/rfw/test/goldens/material_test.drawer.png
Binary files differ
diff --git a/packages/rfw/test/goldens/material_test.dropdown.png b/packages/rfw/test/goldens/material_test.dropdown.png
new file mode 100644
index 0000000..80011c9
--- /dev/null
+++ b/packages/rfw/test/goldens/material_test.dropdown.png
Binary files differ
diff --git a/packages/rfw/test/goldens/material_test.scaffold.png b/packages/rfw/test/goldens/material_test.scaffold.png
index b77bf76..c0e1c74 100644
--- a/packages/rfw/test/goldens/material_test.scaffold.png
+++ b/packages/rfw/test/goldens/material_test.scaffold.png
Binary files differ
diff --git a/packages/rfw/test/material_widgets_test.dart b/packages/rfw/test/material_widgets_test.dart
index 01aefa9..b3f659b 100644
--- a/packages/rfw/test/material_widgets_test.dart
+++ b/packages/rfw/test/material_widgets_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:rfw/formats.dart' show parseLibraryFile;
@@ -123,6 +124,55 @@
                 ),
               ),
             ),
+            Divider(),
+            Padding(
+              padding: [20.0],
+              child: Row(
+                mainAxisAlignment: 'spaceEvenly',
+                children: [
+                  DropdownButton(
+                    value: 'foo',
+                    elevation: 14,
+                    dropdownColor: 0xFF9E9E9E,
+                    underline: Container(
+                      height: 2,
+                      color: 0xFF7C4DFF,
+                    ),
+                    style: {
+                      color:0xFF7C4DFF,
+                    },
+                    items: [
+                      {
+                        value: 'foo',
+                        child: Text(text: 'foo'),
+                      },
+                      {
+                        value: 'bar',
+                        child: Text(text: 'bar'),
+                        onTap: event 'menu_item' { args: 'bar' },
+                      },
+                    ],
+                    borderRadius:[{x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}, {x: 8.0, y: 8.0}],
+                    onChanged: event 'dropdown' {},
+                  ),
+                  DropdownButton(
+                    value: 1.0,
+                    items: [
+                      {
+                        value: 1.0,
+                        child: Text(text: 'first'),
+                      },
+                      {
+                        value: 2.0,
+                        child: Text(text: 'second'),
+                        onTap: event 'menu_item' { args: 'second' },
+                      },
+                    ],
+                    onChanged: event 'dropdown' {},
+                  ),
+                ],
+              ),
+            ),
           ],
         ),
         floatingActionButton: FloatingActionButton(
@@ -137,6 +187,28 @@
       matchesGoldenFile('goldens/material_test.scaffold.png'),
       skip: !runGoldens,
     );
+
+    await tester.tap(find.byType(DropdownButton<Object>).first);
+    await tester.pumpAndSettle();
+    await expectLater(
+      find.byType(MaterialApp),
+      matchesGoldenFile('goldens/material_test.dropdown.png'),
+      skip: !runGoldens,
+    );
+    // Tap on the second item.
+    await tester.tap(find.text('bar'));
+    await tester.pumpAndSettle();
+    expect(eventLog, contains('menu_item {args: bar}'));
+    expect(eventLog, contains('dropdown {value: bar}'));
+
+    await tester.tap(find.byType(DropdownButton<Object>).last);
+    await tester.pumpAndSettle();
+    await tester.tap(find.text('second'));
+    await tester.pumpAndSettle();
+    expect(eventLog, contains('menu_item {args: second}'));
+    expect(eventLog,
+        contains(kIsWeb ? 'dropdown {value: 2}' : 'dropdown {value: 2.0}'));
+
     await tester.tapAt(const Offset(20.0, 20.0));
     await tester.pump();
     await tester.pump(const Duration(seconds: 1));